/*
 *
 *
Copyright (c) 1998-2001 Aristar, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any,
must include the following acknowledgment:

"This product includes software developed by Aristar, Inc. (http://www.aristar.com/)."

Alternately, this acknowledgment may appear in the software itself, if and
wherever such third-party acknowledgments normally appear.

4. The names "Dialect" and "Aristar, Inc." must not be used to endorse or
promote products derived from this software without prior written permission.
For written permission, please contact info@aristar.com.

5. Products derived from this software may not be called "Dialect", nor may
"Dialect" appear in their name, without prior written permission of Aristar, Inc.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
ARISTAR, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software consists of voluntary contributions made by many individuals
on behalf of Aristar, Inc.  For more information on Aristar, Inc.,
please see <http://www.aristar.com/>.

Portions of this software are based upon software freely available and
written by individuals not associated with Aristar, Inc.  Copyrights are
maintained in the relevant source code files.
 *
 *
 */

#include "stdafx.h"
#include "DConfigGlobals.h"
#include <math.h>
#include "COutputDevice.h"
#include "Interpreter.h"
#ifndef DIALECT_LITE
#	include "CMeta.h"
#endif

// For the printdlg
#include <commdlg.h>
#if defined(UNDER_CE) && !defined(WIN32_PLATFORM_PSPC)
#	include <wingdi.h>
#	define TEXTOUT(dc,x,y,pz,c)	::ExtTextOut(dc, x, y, 0, NULL, pz, c, NULL)
#else
#	define TEXTOUT(dc,x,y,pz,c)	::TextOut(dc, x, y, pz, c)
#endif

extern void GuaranteeFilePath(TString filename);	// In CFileModule.cpp

///////////////////////////////////
#if defined(USING_MFC) && defined(_DEBUG)
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////

// Macros from Windowsx.h
#define CB_ResetContent(hwndCtl)          ((int)(DWORD)SendMessage((hwndCtl), CB_RESETCONTENT, 0L, 0L))
#define CB_AddString(hwndCtl, lpsz)       ((int)(DWORD)SendMessage((hwndCtl), CB_ADDSTRING, 0L, (LPARAM)(LPCTSTR)(lpsz)))
#define CB_GetCurSel(hwndCtl)             ((int)(DWORD)SendMessage((hwndCtl), CB_GETCURSEL, 0L, 0L))
#define CB_SetCurSel(hwndCtl, index)      ((int)(DWORD)SendMessage((hwndCtl), CB_SETCURSEL, (WPARAM)(int)(index), 0L))


//=========================================================================
// The purpose of NextWord() is to aid in wrapping text
// Splits up a string at spaces and returns how many
// chars to print and a pointer into the string to
// start printing at.

static LPCTSTR wordBuffer;
static LPCTSTR NextWord(int& count)
{
	int idx = 0;
	if (*wordBuffer == 0) {
		count = -1;
		return NULL;
	}
	while ((wordBuffer[idx] != ' ') && (wordBuffer[idx] != 0)) idx++;
	if (wordBuffer[idx] == 0) idx--;	// Don't print NULL terminator
	count = idx >= 0 ? idx + 1 : -1;
	LPCTSTR result = wordBuffer;
	wordBuffer += idx + 1;
	return result;
}

//=========================================================================

COutputDev::COutputDev()
{
	m_bDoingSItem = false;
	m_SectHeaderTop = -1;
	m_TabSize = 4;	// All inherited classes should override this value
	m_bPrintedHeaders = false;
	m_bPrintingHeadersNow = false;
	m_bPrintingSectionHeaders = false;
	m_PageNumber = 0;
	ClearHeaders();
	m_rcMargins.left = 0;
	m_rcMargins.top = 0;
	m_rcMargins.right = 800;	// Meaningless here, but larger than zero
	m_rcMargins.bottom = 800;	// Meaningless here, but larger than zero
	m_rcTextMargins = m_rcMargins;

	m_FontStyle = FN_NORMAL;
	m_FontName = "Courier";
	m_FontSize = 10;
	m_FillToSpot = 40;
	m_WrapIndent = 0;
}

COutputDev::~COutputDev(){};

void COutputDev::NewPage(){}
void COutputDev::SetMargins(float left, float top, float right, float bottom){}
void COutputDev::TextOutTab(){}
void COutputDev::AbortDocument(){}
void COutputDev::DrawLine(float x1, float y1, float x2, float y2, float width){}

float COutputDev::GetX(){return (float)m_CurrentX;}
float COutputDev::GetY(){return (float)m_CurrentY;}
void COutputDev::SetX(float x){}
void COutputDev::SetY(float y){}
float COutputDev::TextWidth(TString text){return (float)text.GetLength();}

void COutputDev::SectionItem(TString& label, TString& data, TCHAR fillChar)
{
	m_bDoingSItem = true;
	int oldWrapIndent = m_WrapIndent;
	m_WrapIndent = m_FillToSpot;

	if (!m_bPrintingHeadersNow) {
		if (m_SectHeaderTop >= 0) {
			if ((!m_bPrintingSectionHeaders) && (!m_SectionHeaders[m_SectHeaderTop].bPrinted)) {
				// PrintSectionHeaders() calls this TextOut() so we
				// had better kill the ensuing endless loop right now...
				PrintSectionHeaders();
			}
		}
	}

	if (m_CurrentX != m_rcTextMargins.left) {
		TextOutCR();
	}
	TextOut(label);
	if (!data.IsEmpty()) {
		FillTo(m_FillToSpot, fillChar);
		TextOut(_T(" "));
		TextOut(data);
	}
	TextOutCR();

	m_WrapIndent = oldWrapIndent;
	m_bDoingSItem = false;
}

void COutputDev::StartSection(TString& label, TString& data, U32 style)
{
	if (m_SectHeaderTop >= MAX_SECTION_HEADERS - 1) {
		InternalError(_T("Can't support anymore nested sections in output device"));
	}
	else {
		++m_SectHeaderTop;
		m_rcTextMargins.left = m_rcMargins.left + (m_SectHeaderTop + 1) * m_TabSize;
		m_SectionHeaders[m_SectHeaderTop].label = label;
		m_SectionHeaders[m_SectHeaderTop].data = data;
		m_SectionHeaders[m_SectHeaderTop].style = style;
		m_SectionHeaders[m_SectHeaderTop].bPrinted = false;
		if (!data.IsEmpty()) {
			// This item is always going to print, so do it now
			PrintSectionHeaders();
		}
	}
}

void COutputDev::EndSection()
{
	if (m_SectHeaderTop >= 0) {
		--m_SectHeaderTop;
		if (m_CurrentX == m_rcTextMargins.left) {
			// In this case there was the last thing sent was a carraige return
			// so let's move the cursor to the new margin
			m_rcTextMargins.left = m_rcMargins.left + (m_SectHeaderTop + 1) * m_TabSize;
			//m_CurrentX = m_rcTextMargins.left;
			SetCursorColumn(m_rcTextMargins.left);
		}
		else {
			m_rcTextMargins.left = m_rcMargins.left + (m_SectHeaderTop + 1) * m_TabSize;
		}
	}
	else {
		SyntaxError(_T("Too many EndSections called on output device"));
	}
}

void COutputDev::EndAllSections()
{
	while (m_SectHeaderTop >= 0) {
		EndSection();
	}
}

int COutputDev::NumUnprintedSectHeaders()
{
	int result = 0;
	for (int idx = 0; idx <= m_SectHeaderTop; ++idx) {
		if (!m_SectionHeaders[idx].bPrinted) {
			// Actually, every header above the first non-printed
			// one should also be non-printed...
			++result;
		}
	}
	return result;
}

void COutputDev::PrintSectionHeaders()
{
	// Start at header zero and print any headers not printed yet
	// The current creation and destruction of fonts is slow,
	// unless Windows is caching these things... fix this later...

	m_bPrintingSectionHeaders = true;

	// Don't forget about text margins, etc
	RECT oldMargins = m_rcTextMargins;
	m_rcTextMargins = m_rcMargins;
	U32 oldFontStyle = m_FontStyle;

	if (m_bPrintedHeaders) {
		// Only do this is not at very top of page
		TextOutCR();
	}

	bool bNewPage = false;
	for (int idx = 0; idx <= m_SectHeaderTop; ++idx) {
		if (!m_SectionHeaders[idx].bPrinted) {
			if (!bNewPage) {
				bNewPage = true;	// Only do this once
				int linesToPrint = m_SectHeaderTop - idx + 2;
				if (LinesLeft() < linesToPrint) NewPage();
			}
			m_SectionHeaders[idx].bPrinted = true;
			if (m_CurrentX != m_rcTextMargins.left)	TextOutCR();
			if (m_SectionHeaders[idx].style != m_FontStyle) {
				SetFont(m_FontName, m_FontSize, m_SectionHeaders[idx].style);
			}
			TextOut(m_SectionHeaders[idx].label);
			if (m_FontStyle != oldFontStyle) {
				SetFont(m_FontName, m_FontSize, oldFontStyle);
			}
			if (!m_SectionHeaders[idx].data.IsEmpty()) {
				FillTo(m_FillToSpot, '.');
				TextOut(_T(" "));
				TextOut(m_SectionHeaders[idx].data);
			}
			//m_rcTextMargins.left += m_TabSize;
			//m_CurrentX = m_rcTextMargins.left;
			TextOutCR();
		}
		m_rcTextMargins.left += m_TabSize;
		m_CurrentX = m_rcTextMargins.left;
	}

	m_rcTextMargins = oldMargins;
	m_CurrentX = oldMargins.left;

	if (m_FontStyle != oldFontStyle) {
		m_FontStyle = oldFontStyle;
		SetFont(m_FontName, m_FontSize, m_FontStyle);
	}

	m_bPrintingSectionHeaders = false;
}

void COutputDev::ClearHeaders()
{
	for (int idx = 0; idx < MAX_PAGE_HEADERS; idx++) {
		m_PageHeaders[idx].Empty();
	}
	m_HeaderCount = 0;
}

void COutputDev::SetPageHeader(int index, LPCTSTR headerText)
{
	if (index > MAX_PAGE_HEADERS) {
		ASSERT(false);
		SyntaxError(_T("Can only have page header numbers up to %d"), MAX_PAGE_HEADERS);
	}
	// Index is 1 based index in m_PageHeaders array
	if (index > m_HeaderCount) m_HeaderCount = index;
	m_PageHeaders[index-1] = headerText;
}

void COutputDev::PrintPageHeaders()
{
	m_bPrintedHeaders = true;
	m_bPrintingHeadersNow = true;

	int pos;
	TString header;
	RECT oldTextMargins = m_rcTextMargins;
	if (m_CurrentX == m_rcTextMargins.left) {
		SetCursorColumn(m_rcMargins.left);
	}
	m_rcTextMargins = m_rcMargins;	// Always print headers using full page margins
	if (m_CurrentX != m_rcTextMargins.left) {
		TextOutCR();
	}

	U32 oldFontStyle = m_FontStyle;
	if ((m_HeaderCount > 0) && (m_FontStyle != FN_BOLD)) {
		oldFontStyle = m_FontStyle;
		SetFont(m_FontName, m_FontSize, FN_BOLD);
	}

	for (int idx = 0; idx < m_HeaderCount; idx++) {
		header = m_PageHeaders[idx];

		header.MakeLower();		// So the search for &page is case insensitive
		pos = header.Find(_T("&page"));

		header = m_PageHeaders[idx];	// To undo the MakeLower call
		if (pos != -1) {
			header.Format(_T("%s%d%s"), (LPCTSTR)header.Left(pos), m_PageNumber, (LPCTSTR)header.Mid(pos + 5));
		}
		TextOut(header);
		TextOutCR();
	}
	m_rcTextMargins = oldTextMargins;	// Reset text margins
	if (m_HeaderCount > 0) TextOutCR();	// Let's add one blank line

	if (oldFontStyle != m_FontStyle) {
		SetFont(m_FontName, m_FontSize, oldFontStyle);
	}

	m_bPrintingHeadersNow = false;
}

void COutputDev::FillTo(int column, TCHAR fillChar)
{
	RunTimeError(_T("FillTo unsupported for this output device"));
}


//=====================================================================

/*======================================================================
The relevant documentation for EndPage() is:
"...
Windows 3.x: EndPage resets the device context attributes back to default
  values. You must re-select objects and set up the mapping mode again
  before printing the next page.

Windows 95: EndPage does not reset the device context attributes. However,
  the next StartPage call does reset the device context attributes to default
  values. At that time, you must re-select objects and set up the mapping
  mode again before printing the next page.

Windows NT: Beginning with Windows NT Version 3.5, neither EndPage or
  StartPage resets the device context attributes. Device context attributes
  remain constant across subsequent pages. You do not need to re-select
  objects and set up the mapping mode again before printing the next page;
  however, doing so will produce the same results and reduce code
  differences between Windows 95 and Windows NT.
..."
*///======================================================================

//===========================================================================

#if !defined(WIN32_PLATFORM_PSPC)

CPrinterOutputDev::CPrinterOutputDev(LPCTSTR UNCPrinterName)
{
	bPrintingDoc = false;
	bPrintingPage = false;

	m_hFont = NULL;

	if ((UNCPrinterName != NULL) && (*UNCPrinterName == '\0')) {
		UNCPrinterName = NULL;
	}

	if (UNCPrinterName != NULL) {
		m_hPrinterDC = CreateDC(NULL, UNCPrinterName, NULL, NULL);
	}
	else {
		// Set the flags to get DC for default printer without
		// showing printer dialog box.
		PRINTDLG pd;
		memset(&pd, 0, sizeof(PRINTDLG));
#ifdef UNDER_CE
		pd.cbStruct = sizeof(PRINTDLG);
		//pd.dwFlags = PD_RETURNDEFAULTDC;
		PrintDlg(&pd);		// Get the info now
		m_hPrinterDC = pd.hdc;
#else
		pd.lStructSize = sizeof(PRINTDLG);
		pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
		PrintDlg(&pd);		// Get the info now
		m_hPrinterDC = pd.hDC;
#endif
	}

	if (m_hPrinterDC == NULL) {
		TString error;
		if (UNCPrinterName == NULL) error.Format(_T("Could not open specified printer: default"));
		else error.Format(_T("Could not open specified printer: %s"), UNCPrinterName);
		RunTimeError(error);
	}
	else {
		m_ptPixPerInch.x = GetDeviceCaps(m_hPrinterDC, LOGPIXELSX);
		m_ptPixPerInch.y = GetDeviceCaps(m_hPrinterDC, LOGPIXELSY);
#ifdef UNDER_CE
		m_ptGutter.x = GetDeviceCaps(m_hPrinterDC, PHYSICALOFFSETX);
		m_ptGutter.y = GetDeviceCaps(m_hPrinterDC, PHYSICALOFFSETY);
		m_ptPageSize.x = GetDeviceCaps(m_hPrinterDC, PHYSICALWIDTH);
		m_ptPageSize.y = GetDeviceCaps(m_hPrinterDC, PHYSICALHEIGHT);
#else
		Escape(m_hPrinterDC, GETPRINTINGOFFSET, 0, NULL, &m_ptGutter);
		Escape(m_hPrinterDC, GETPHYSPAGESIZE, 0, NULL, &m_ptPageSize);
#endif
	}

	m_TabSize = m_ptPixPerInch.x >> 2;	// Quarter inch per tab
	m_FillToSpot = 37;	// Used in SectionItem();
}

CPrinterOutputDev::~CPrinterOutputDev()
{
	EndDocument();
	if (m_hFont != NULL) {
		DeleteObject(m_hFont);		// Discard old
	}
	DeleteDC(m_hPrinterDC);
}

// Set/Get in units of inches...
float CPrinterOutputDev::GetX(){return (float)m_CurrentX / (float)m_ptPixPerInch.x;}
float CPrinterOutputDev::GetY(){
	float y = (float)m_CurrentY / (float)m_ptPixPerInch.y;
	return y;
}
void CPrinterOutputDev::SetX(float x){m_CurrentX = (int)(x * m_ptPixPerInch.x) - m_ptGutter.x;}
void CPrinterOutputDev::SetY(float y){
	m_CurrentY = (int)(y * m_ptPixPerInch.y) /*- m_ptGutter.y*/;
}
float CPrinterOutputDev::TextWidth(TString text) {
	SIZE extent;
	GetTextExtentPoint32(m_hPrinterDC, (LPCTSTR)text, text.GetLength(), &extent);
	return (float)extent.cx / (float)m_ptPixPerInch.x;
}

void CPrinterOutputDev::SetMargins(float left, float top, float right, float bottom)
{
	int leftOffset = m_rcTextMargins.left - m_rcMargins.left;	// Current "tabbing" level

	// Convert inches to pixel values respective to top and left
	m_rcMargins.left = (int)ceil(left * (float)m_ptPixPerInch.x) - m_ptGutter.x;
	m_rcMargins.top = (int)ceil(top * (float)m_ptPixPerInch.y) - m_ptGutter.y;
	m_rcMargins.right = m_ptPageSize.x - (int)ceil(right * (float)m_ptPixPerInch.x) - m_ptGutter.x;
	m_rcMargins.bottom = m_ptPageSize.y - (int)ceil(bottom * (float)m_ptPixPerInch.y) - m_ptGutter.y;

	if (!m_bPrintedHeaders) {
		// We haven't printed anything yet, so we can still move the "cursor"
		// to the top-left corner without fear of overwritting previous text.
		m_CurrentX = m_rcMargins.left;
		m_CurrentY = m_rcMargins.top;
	}

	m_rcTextMargins = m_rcMargins;
	m_rcTextMargins.left = m_rcMargins.left + leftOffset;
}

void CPrinterOutputDev::AbortDocument()
{
	if (bPrintingDoc) ::AbortDoc(m_hPrinterDC);
	bPrintingPage = false;
	bPrintingDoc = false;
	m_bPrintedHeaders = false;
	if (m_SectHeaderTop != -1) {
		m_SectHeaderTop = -1;
		//SyntaxError(_T("Missing a matching output endsect in document"));
	}
}

void CPrinterOutputDev::EndDocument()
{
	if (bPrintingDoc) {
		if (m_PageNumber == 0) {
			// If we haven't printer anything yet, and document is done, then
			// abort so that we don't print a blank page.
			::AbortDoc(m_hPrinterDC);
		}
		else {
			if (bPrintingPage) {
				::EndPage(m_hPrinterDC);
			}
			if (bPrintingDoc) {
				::EndDoc(m_hPrinterDC);
			}
		}
	}
	bPrintingPage = false;
	bPrintingDoc = false;
	m_bPrintedHeaders = false;
	if (m_SectHeaderTop != -1) {
		EndAllSections();
		//m_SectHeaderTop = -1;
		//SyntaxError(_T("Missing a matching output endsect in document"));
	}
}

void CPrinterOutputDev::StartDocument(LPCTSTR docName)
{
	EndDocument();		// Be sure to end last doc first

	m_DocName = docName;
	m_PageNumber = 0;	// The following NewPage() will increment this to 1
	ClearHeaders();
	SetMargins(1.0f, 1.0f, 1.0f, 1.0f);
	SetFont(_T("Arial"), 8, FN_NORMAL);
	//SetFont("Courier New", 9, FN_NORMAL);
	m_CurrentX = m_rcTextMargins.left;
	m_CurrentY = m_rcTextMargins.top;

	//DOCINFO di;
	//di.cbSize = sizeof(di);
	//di.lpszDocName = (LPCTSTR)m_DocName;
	//di.lpszOutput = NULL;
	//di.lpszDatatype = NULL;
	//di.fwType = 0;
	//::StartDoc(m_hPrinterDC, &di);
	//bPrintingDoc = true;
	//NewPage();
}

void CPrinterOutputDev::NewPage()
{
	if (m_PageNumber == 0) {
		DOCINFO di;
		di.cbSize = sizeof(di);
		di.lpszDocName = (LPCTSTR)m_DocName;
		di.lpszOutput = NULL;
		di.lpszDatatype = NULL;
		di.fwType = 0;
		::StartDoc(m_hPrinterDC, &di);
		bPrintingDoc = true;
	}

	if (bPrintingPage) {
		::EndPage(m_hPrinterDC);
	}
	::StartPage(m_hPrinterDC);
	// Must reselect font under Windows 95/3.1 (not needed under NT)
	if (!SelectObject(m_hPrinterDC, m_hFont)) {
		OutputDebugString(_T("** Failed to select font into device\n"));
	}

	m_bPrintedHeaders = false;
	bPrintingPage = true;
	m_PageNumber++;
	m_CurrentX = m_rcTextMargins.left;
	m_CurrentY = m_rcTextMargins.top;
}

void CPrinterOutputDev::FillTo(int column, TCHAR fillChar)
{
	if (fillChar < ' ') return;

	if (m_PageNumber == 0) NewPage();

	// Fill to uses the page margins, not the text margins
	// to calculate spacing

	SIZE extent;
	GetTextExtentPoint32(m_hPrinterDC, &fillChar, 1, &extent);

	// Fill backwards so that the end of the fill is exactly placed
	int leftX = m_CurrentX;
	// Switch to 10ths of an inch?
	int rightX = m_rcMargins.left + column / 10 * m_ptPixPerInch.x;

	if (rightX < leftX) return;	// Nothing to do

	m_CurrentX = rightX + extent.cx;	// Start following textout ops here when done with fill

	while (rightX > leftX) {
		// Would be faster to create a string of correct length
		// and then do only one textout call
		TEXTOUT(m_hPrinterDC, rightX, m_CurrentY, &fillChar, 1);
		rightX -= extent.cx;
	}
}

void CPrinterOutputDev::TextOut(LPCTSTR text)
{
	if (text == NULL) return;

	int length = _tcslen(text);
	TCHAR* buf = new TCHAR[length + 1];
	TCHAR* bufPtr = buf;
	for (int loop = 0; loop < length; ++loop) {
		if ((BYTE)((*bufPtr = text[loop])) >= ' ') ++bufPtr;
		else {
			// Assuming this is a '\n', '\r', or '\r\n'
			*bufPtr = 0;
			_TextOut(buf);
			TextOutCR();
			if (m_bDoingSItem) {
				FillTo(m_FillToSpot, ' ');
				_TextOut(_T(" "));
			}
			bufPtr = buf;
			if ((text[loop + 1] < ' ') && (text[loop] != text[loop + 1])) ++loop;
		}
	}
	*bufPtr = 0;
	_TextOut(buf);
	delete[] buf;
}

void CPrinterOutputDev::PrintHeadersIfNeeded()
{
	if (!m_bPrintedHeaders) {
		// Print page headers at the start of every page
		m_bPrintedHeaders = true;
		PrintPageHeaders();
	}

	if (!m_bPrintingHeadersNow) {
		if (m_SectHeaderTop >= 0) {
			if ((!m_bPrintingSectionHeaders) && (!m_SectionHeaders[m_SectHeaderTop].bPrinted)) {
				// PrintSectionHeaders() calls this TextOut() so we
				// had better kill the ensuing endless loop right now...
				PrintSectionHeaders();
			}
		}
	}

	if (!m_bPrintedHeaders) {
		// Printing the above section headers could have caused a new page
		// Which would now require page headers
		m_bPrintedHeaders = true;
		PrintPageHeaders();
	}
}

void CPrinterOutputDev::_TextOut(LPCTSTR text)
{
	ASSERT(text != NULL);

	if (*text == 0) return;		// Empty string, nothing to print

	if (m_PageNumber == 0) NewPage();

	PrintHeadersIfNeeded();

	int textLength = _tcslen(text);
	SIZE extent;
	GetTextExtentPoint32(m_hPrinterDC, text, textLength, &extent);

	if (m_rcTextMargins.right - m_CurrentX >= extent.cx) {
		TEXTOUT(m_hPrinterDC, m_CurrentX, m_CurrentY, text, textLength);
		m_CurrentX += extent.cx;
	}
	else {
		int idx;
		wordBuffer = text;
		LPCTSTR s = NextWord(idx);
		while (idx > -1) {	// NextWord sets idx to -1 when nothing left
			GetTextExtentPoint32(m_hPrinterDC, s, idx, &extent);
			/*if (m_rcTextMargins.right - m_rcTextMargins.left < extent.cx) {
				// It's not going to fit after a CR anyway, so just print it...
			}
			else*/ if (m_rcTextMargins.right - m_CurrentX < extent.cx) {
				TextOutCR();
				// We may have wrapped to a new page that might need headers...
				PrintHeadersIfNeeded();
				// We need to "tab" to the wrap indent level here
				if (m_bDoingSItem) {
					FillTo(m_FillToSpot, ' ');
					_TextOut(_T(" "));	// One space to help align
				}
				//else {
				//	SetColumn(m_WrapIndent);
				//}
				// After a carraige return, don't print preceeding spaces?
				while ((idx > 0) && (*s == ' ')) {
					s++;
					idx--;
				}
			}

			if (idx > -1) {
				if (idx > 0) {
					TEXTOUT(m_hPrinterDC, m_CurrentX, m_CurrentY, s, idx);
					m_CurrentX += extent.cx;
				}
				s = NextWord(idx);
			}
		}
	}
}

void CPrinterOutputDev::TextOutTab()
{
	if (m_PageNumber == 0) NewPage();

	m_CurrentX += m_TabSize - (m_CurrentX - m_rcTextMargins.left) % m_TabSize;
	if (m_CurrentX > m_rcTextMargins.right) {
		m_CurrentX = m_rcTextMargins.right;
	}
}

void CPrinterOutputDev::TextOutCR(int lineHeight)
{
	if (m_PageNumber == 0) NewPage();

	if (lineHeight < 0) lineHeight = m_LineHeight;

	if (m_CurrentY + lineHeight > m_rcTextMargins.bottom) {
		NewPage();
	}
	else {
		m_CurrentX = m_rcTextMargins.left;
		m_CurrentY += lineHeight;
	}
}

int CPrinterOutputDev::LinesLeft()
{
	return (m_rcTextMargins.bottom - m_CurrentY) / m_LineHeight;
}

void CPrinterOutputDev::DrawLine(float x1, float y1, float x2, float y2, float width)
{
	POINT old;
	CPen pen;
	int iWidth = (int)(width * (float)m_ptPixPerInch.x);
	pen.CreatePen(PS_SOLID, iWidth > 0 ? iWidth : 1, RGB(0, 0, 0));
	HGDIOBJ hOld = SelectObject(m_hPrinterDC, pen);
#ifdef UNDER_CE
	#include <afxwin.h>
	CDC* pDC = CDC::FromHandle(m_hPrinterDC);
	if (pDC != NULL) {
		old = pDC->GetCurrentPosition();
		pDC->MoveTo((int)(x1 * (float)m_ptPixPerInch.x) - m_ptGutter.x, (int)(y1 * (float)m_ptPixPerInch.y) - m_ptGutter.y);
		pDC->LineTo((int)(x2 * (float)m_ptPixPerInch.x) - m_ptGutter.x, (int)(y2 * (float)m_ptPixPerInch.y) - m_ptGutter.y);
		pDC->MoveTo(old.x, old.y);
	}
#else
	MoveToEx(m_hPrinterDC, (int)(x1 * (float)m_ptPixPerInch.x) - m_ptGutter.x, (int)(y1 * (float)m_ptPixPerInch.y) - m_ptGutter.y, &old);
	LineTo(m_hPrinterDC, (int)(x2 * (float)m_ptPixPerInch.x) - m_ptGutter.x, (int)(y2 * (float)m_ptPixPerInch.y) - m_ptGutter.y);
	MoveToEx(m_hPrinterDC, old.x, old.y, NULL);
#endif
	SelectObject(m_hPrinterDC, hOld);
}

void CPrinterOutputDev::SetFont(TString fontName, int fontSize, U32 fontStyle)
{
#ifdef UNDER_CE
	LOGFONT lf;
	memset(&lf, 0, sizeof(LOGFONT));
	lf.lfHeight = -((fontSize * GetDeviceCaps(m_hPrinterDC, LOGPIXELSY)) / 72);
	lf.lfWidth = 0;
	lf.lfWeight = fontStyle & FN_BOLD ? FW_BOLD : FW_NORMAL;
	lf.lfItalic = fontStyle & FN_ITALIC ? TRUE : FALSE;
	lf.lfUnderline = fontStyle & FN_UNDERLINE ? TRUE : FALSE;
	lf.lfCharSet = DEFAULT_CHARSET;
	lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
	lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
	lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
	_tcsncpy(lf.lfFaceName, (LPCTSTR)fontName, LF_FACESIZE - 1);
	HFONT newFont = CreateFontIndirect(&lf);
#else
	HFONT newFont = CreateFont(
		-MulDiv(fontSize, GetDeviceCaps(m_hPrinterDC, LOGPIXELSY), 72),	// logical height of font
		0, 											// logical average character width
		0,											// angle of escapement
		0,											// base-line orientation angle
		fontStyle & FN_BOLD ? FW_BOLD : FW_NORMAL,	// font weight
		fontStyle & FN_ITALIC ? TRUE : FALSE,		// italic attribute flag
		fontStyle & FN_UNDERLINE ? TRUE : FALSE,	// underline attribute flag
		FALSE,										// strikeout attribute flag
		DEFAULT_CHARSET,							// character set identifier
		OUT_DEFAULT_PRECIS,							// output precision
		CLIP_DEFAULT_PRECIS,						// clipping precision
		PROOF_QUALITY,								// output quality
		DEFAULT_PITCH | FF_DONTCARE,				// pitch and family
		(LPCTSTR)fontName							// pointer to typeface name string
	);
#endif

	if (newFont == NULL) {
		MessageBox(NULL, _T("Failed to create font."), _T("Warning"), MB_OK);
	}
	else {
		m_FontName = fontName;
		m_FontSize = fontSize;
		m_FontStyle = fontStyle;
		if (!SelectObject(m_hPrinterDC, newFont)) {	// Select in new font
			OutputDebugString(_T("** Failed to select font into device\n"));
		}
		if (m_hFont != NULL) {
			DeleteObject(m_hFont);				// Discard old font
		}
		m_hFont = newFont;						// Save the pointer for later

		GetTextMetrics(m_hPrinterDC, &m_TextMetrics);
		m_LineHeight = m_TextMetrics.tmHeight + 2 * m_TextMetrics.tmExternalLeading;
	}
}

void CPrinterOutputDev::PrintTable(const TTable& table)
{
	if (m_PageNumber == 0) NewPage();

	// First, let's make sure we really need to print this table
	bool bPrintingOK = false;
	int firstCol = table.m_bRowHeaders ? 1 : 0;
	for (int loop = firstCol; loop < table.m_Cols; ++loop) {
		if (table.m_bEmptyCol[loop] == false) {
			bPrintingOK = true;
			break;
		}
	}
	if (!bPrintingOK) return;

	int* colWidths = new int[table.m_Cols];
	int* rowHeights = new int[table.m_Rows];
	memset(colWidths, 0, sizeof(int) * table.m_Cols);
	memset(rowHeights, 0, sizeof(int) * table.m_Rows);
	DetermineColWidths(table, colWidths, rowHeights);

	int TableHeight = 0;
	for (loop = 0; loop < table.m_Rows; ++loop) {
		TableHeight += rowHeights[loop];
	}

	// Make sure we are all the way to the left margin
	if (m_CurrentX != m_rcTextMargins.left) TextOutCR();

	// If the table won't fit completely on a page, start a new page
	// Unless, of course, it will never fit on a whole page anyway
	int TotalHeight = TableHeight + NumUnprintedSectHeaders() * m_LineHeight;
	if ((m_CurrentY + TotalHeight > m_rcTextMargins.bottom) &&
		(TotalHeight < m_rcTextMargins.bottom - m_rcTextMargins.top))
	{
		NewPage();
	}

	// We don't want headers to print during a tables call to TextOut()
	// becuase we will have messed with the margins
	if (!m_bPrintedHeaders) PrintPageHeaders();
	if (!m_bPrintingHeadersNow) {
		if (m_SectHeaderTop >= 0) {
			if ((!m_bPrintingSectionHeaders) && (!m_SectionHeaders[m_SectHeaderTop].bPrinted)) {
				// PrintSectionHeaders() calls this TextOut() so we
				// had better kill the ensuing endless loop right now...
				PrintSectionHeaders();
			}
		}
	}

	TString str;
	for (int row = 0; row < table.m_Rows; ++row) {
		if (table.m_bEmptyRow[row] == false) {
			for (int col = 0; col < table.m_Cols; ++col) {
				if (table.m_bEmptyCol[col] == false) {
					int oldIndent = m_WrapIndent;
					m_WrapIndent = 0;
					RECT oldMargins = m_rcTextMargins;

					int startX = m_CurrentX;
					int startY = m_CurrentY;

					m_rcTextMargins.top = m_CurrentY;
					m_rcTextMargins.bottom = oldMargins.bottom;
					m_rcTextMargins.left = m_CurrentX;
					m_rcTextMargins.right = m_CurrentX + colWidths[col];

					TextOut((LPCTSTR)table.GetCell(row, col).contents);

					m_CurrentX = m_rcTextMargins.right + (m_ptPixPerInch.x >> 2);	// Quarter inch pad
					m_CurrentY = startY;

					m_rcTextMargins = oldMargins;
					m_WrapIndent = oldIndent;
				}
			}
			TextOutCR(rowHeights[row]);
		}
	}

	delete[] colWidths;
	delete[] rowHeights;
}

void CPrinterOutputDev::DetermineColWidths(const TTable& table, int colWidths[], int rowHeights[])
{
	// The array colWidths must be zero'd before calling this function
	int Length;
	for (int row = 0; row < table.m_Rows; ++row) {
		if (table.m_bEmptyRow[row] == false) {
			for (int col = 0; col < table.m_Cols; ++col) {
				if (table.m_bEmptyCol[col] == false) {
					SIZE extent;
					GetTextExtentPoint32(m_hPrinterDC,
						(LPCTSTR)table.GetCell(row, col).contents,
						table.GetCell(row, col).contents.GetLength(),
						&extent);
					Length = extent.cx;
					if (colWidths[col] < Length) {
						colWidths[col] = Length;
					}
				}
				else {
					colWidths[col] = 0;
				}
			}
		}
	}

	DetermineRowHeights(table, colWidths, rowHeights);
}

void CPrinterOutputDev::DetermineRowHeights(const TTable& table, int colWidths[], int rowHeights[])
{
	int totalWidth = 0, lastTotalWidth = 100000;
	float maxColWidth = 2.0f;	// Start with 2 inches

	// A hack job, try 'loop' number of times to reduce length
	for (int loop = 0; loop < 1000; ++loop) {
		totalWidth = 0;
		for (int col = 0; col < table.m_Cols; ++col) {
			if (col == table.m_Cols - 1)
				totalWidth += colWidths[col];	// Don't pad last cell
			else
				totalWidth += colWidths[col] + (m_ptPixPerInch.x >> 2);// Quarter inch pad
		}

		if (totalWidth == lastTotalWidth) {
			// Reduction isn't happening
			maxColWidth -= 0.2f;
		}
		lastTotalWidth = totalWidth;

		if (totalWidth > m_rcTextMargins.right - m_rcTextMargins.left) {
			// We're going to have to wrap at least one column
			for (int col = 0; col < table.m_Cols; ++col) {
				if (colWidths[col] > (int)(maxColWidth * m_ptPixPerInch.x)) {
					// For each column greater than maxColWidth inches, shorten quarter inch
					colWidths[col] -= (m_ptPixPerInch.x >> 2);
				}
			}
		}
		else break;
	}

	if (totalWidth > m_rcTextMargins.right - m_rcTextMargins.left) {
		InternalError(_T("Table too wide for page"));
	}

	int Height;
	for (int row = 0; row < table.m_Rows; ++row) {
		rowHeights[row] = 0;
		if (table.m_bEmptyRow[row] == false) {
			for (int col = 0; col < table.m_Cols; ++col) {
				if (table.m_bEmptyCol[col] == false) {
					/*SIZE extent;
					GetTextExtentPoint32(m_hPrinterDC,
						(LPCTSTR)table.GetCell(row, col).contents,
						table.GetCell(row, col).contents.GetLength(),
						&extent);*/
					// Number of text lines * line height
					if (colWidths[col] != 0) {
						/*if (extent.cx % colWidths[col] == 0) {
							Height = (extent.cx / colWidths[col]) * m_LineHeight;
						}
						else {
							Height = ((extent.cx / colWidths[col]) + 1) * m_LineHeight;
						}*/
						Height = HowManyLines((LPCTSTR)table.GetCell(row, col).contents, colWidths[col]) * m_LineHeight;
						if (rowHeights[row] < Height) {
							rowHeights[row] = Height;
						}
					}
				}
			}
		}
		else {
			rowHeights[row] = 0;
		}
	}
}

int CPrinterOutputDev::HowManyLines(LPCTSTR text, int width)
{
	// Use for table row height calculations only!!!!!!!!
	if ((text == NULL) || (*text == '\0')) return 0;

	int lineCount = 1, currentX = 0;

	int length = _tcslen(text);
	TCHAR* buf = new TCHAR[length + 1];
	TCHAR* bufPtr = buf;
	for (int loop = 0; loop < length; ++loop) {
		if ((*bufPtr = text[loop]) >= ' ') ++bufPtr;
		else {
			// Assuming this is a '\n', '\r', or '\r\n'
			*bufPtr = 0;
			_HowManyLinesTextOut(buf, width, currentX, lineCount);
			// Carriage return
			++lineCount;
			currentX = 0;

			bufPtr = buf;
			if ((text[loop + 1] < ' ') && (text[loop] != text[loop + 1])) ++loop;
		}
	}
	*bufPtr = 0;
	if (*text != '\0') _HowManyLinesTextOut(buf, width, currentX, lineCount);

	delete[] buf;
	return lineCount;
}

void CPrinterOutputDev::_HowManyLinesTextOut(LPCTSTR text, int width, int& currentX, int& lineCount)
{
	ASSERT(text != NULL);

	if (*text == 0) return;		// Empty string, nothing to print

	int textLength = _tcslen(text);
	SIZE extent;
	GetTextExtentPoint32(m_hPrinterDC, text, textLength, &extent);

	if (width - currentX >= extent.cx) {
		currentX += extent.cx;
	}
	else {
		int idx;
		wordBuffer = text;
		LPCTSTR s = NextWord(idx);
		while (idx > -1) {	// NextWord sets idx to -1 when nothing left
			GetTextExtentPoint32(m_hPrinterDC, s, idx, &extent);
			if (width - currentX < extent.cx) {
				// Carriage return
				++lineCount;
				currentX = 0;
				// After a carraige return, don't print preceeding spaces?
				while ((idx > 0) && (*s == ' ')) {
					s++;
					idx--;
				}
			}

			if (idx > -1) {
				if (idx > 0) {
					currentX += extent.cx;
				}
				s = NextWord(idx);
			}
		}
	}
}

#endif	// #if !defined(WIN32_PLATFORM_PSPC)

//===========================================================================
// m_vHwndToDev is a static member variable and therefore the
// actual storage for it must be created here.

VALUE CEditOutputDev::m_vHwndToDev = AllocFrame(31);
HWND CreateEditWndDlg(HINSTANCE hinst, HWND hwndOwner);
#define IDC_SCREEN_EDIT		23421

CEditOutputDev::CEditOutputDev(CInterpreter* pInterp)
{
	m_TabSize = 2;		// Two chars per tab
	m_FillToSpot = 35;	// Used in SectionItem();
	m_pInterp = pInterp;// Tell the interpreter when window is visible
						// so it knows not to exit until the window is closed

	m_hOutputDlg = CreateEditWndDlg(NULL, NULL);

	if (m_hOutputDlg == NULL) {
		TString err;
		err.Format(_T("Failed to create output window: WinErr = %d"), GetLastError());
		pInterp->RuntimeError((LPCTSTR)err);
	}
	else {
		TString temp;
		temp.Format(_T("%d"), m_hOutputDlg);
		m_vHwndToDev.Set(temp, (int)this);
		if (::IsWindowVisible(m_hOutputDlg)) {
			m_pInterp->IncOpenWindows();
		}
	}
}

CEditOutputDev::~CEditOutputDev()
{
	TString temp;
	temp.Format(_T("%d"), m_hOutputDlg);
	if (m_vHwndToDev.Frame() != NULL) m_vHwndToDev.Frame()->Remove(temp);
	if (::IsWindowVisible(m_hOutputDlg)) m_pInterp->DecOpenWindows();

	DestroyWindow(m_hOutputDlg);
}

void CEditOutputDev::ShowWindow()
{
	if (!::IsWindowVisible(m_hOutputDlg)) {
		::ShowWindow(m_hOutputDlg, SW_SHOW);
		m_pInterp->IncOpenWindows();
	}
}

void CEditOutputDev::StartDocument(LPCTSTR docName)
{
	EndDocument();
	m_PageNumber = 1;	// All pages on the screen are currently page 1
	m_bPrintedHeaders = false;
	ClearHeaders();
	m_DocName = docName;
	ShowWindow();
}

void CEditOutputDev::EndDocument(){}

void CEditOutputDev::TextOutCR(int lineHeight)
{
	ShowWindow();

	if (lineHeight < 0) lineHeight = 1;

	for (int idx = 0; idx < lineHeight; ++idx) {
		// Edit boxes want \r\n for carraige returns
		SendMessage(GetDlgItem(m_hOutputDlg, IDC_SCREEN_EDIT), EM_SETSEL, (WPARAM)-1, (LPARAM)-1);
		SendMessage(GetDlgItem(m_hOutputDlg, IDC_SCREEN_EDIT), EM_REPLACESEL, FALSE, (LPARAM)_T("\r\n"));
	}

	m_CurrentX = 0;
	if (m_rcTextMargins.left > 0) FillTo(m_rcTextMargins.left, ' ');
}

void CEditOutputDev::TextOut(LPCTSTR text)
{
	ShowWindow();

	if (!m_bPrintedHeaders) {
		// Print page headers at the start of every page
		m_bPrintedHeaders = true;
		PrintPageHeaders();
	}

	if (!m_bPrintingHeadersNow) {
		if (m_SectHeaderTop >= 0) {
			if ((!m_bPrintingSectionHeaders) && (!m_SectionHeaders[m_SectHeaderTop].bPrinted)) {
				// PrintSectionHeaders() calls this TextOut() so we
				// had better kill the ensuing endless loop right now...
				PrintSectionHeaders();
			}
		}
	}

	SendMessage(GetDlgItem(m_hOutputDlg, IDC_SCREEN_EDIT), EM_SETSEL, (WPARAM)-1, (LPARAM)-1);
	SendMessage(GetDlgItem(m_hOutputDlg, IDC_SCREEN_EDIT), EM_REPLACESEL, FALSE, (LPARAM)text);
	m_CurrentX += _tcslen(text);
}

int CEditOutputDev::LinesLeft()
{
	return 10000;	// Arbitrary, meaningless large number
}

void CEditOutputDev::SetFont(TString fontName, int fontSize, U32 fontStyle)
{
	// Ignore this for now
}

void CEditOutputDev::FillTo(int column, TCHAR fillChar)
{
	int nChars = column - m_CurrentX;
	if (nChars > 0) {
		TCHAR* text = new TCHAR[nChars + 1];
		// Doing the loop here is faster than repeating Edit_ReplaceSel()
		// once for each char...
		for (int idx = 0; idx < nChars; ++idx) {
			text[idx] = fillChar;
		}
		text[nChars] = '\0';

		SendMessage(GetDlgItem(m_hOutputDlg, IDC_SCREEN_EDIT), EM_SETSEL, (WPARAM)-1, (LPARAM)-1);
		SendMessage(GetDlgItem(m_hOutputDlg, IDC_SCREEN_EDIT), EM_REPLACESEL, FALSE, (LPARAM)text);
		m_CurrentX = column;
		delete[] text;
	}
}

void CEditOutputDev::PrintSectionHeaders()
{
	// Start at header zero and print any headers not printed yet
	// The current creation and destruction of fonts is slow,
	// unless Windows is caching these things... fix this later...

	m_bPrintingSectionHeaders = true;

	// Don't forget about text margins, etc
	int oldMargin = m_rcTextMargins.left;
	if (m_CurrentX == m_rcTextMargins.left) {
		// We need to delete the leading spaces here, since just moving
		// the margin doesn't really move the cursor in an edit window
		m_rcTextMargins.left = 0;
		SetCursorColumn(0);
	}
	else {
		m_rcTextMargins.left = 0;
		TextOutCR();
	}

	for (int idx = 0; idx <= m_SectHeaderTop; ++idx) {
		if (!m_SectionHeaders[idx].bPrinted) {
			m_SectionHeaders[idx].bPrinted = true;
			//SetCursorColumn(m_rcTextMargins.left);
			TextOut(m_SectionHeaders[idx].label);
			if (!m_SectionHeaders[idx].data.IsEmpty()) {
				FillTo(m_FillToSpot, '.');
				TextOut(_T(" "));
				TextOut(m_SectionHeaders[idx].data);
			}
			m_rcTextMargins.left += m_TabSize;
			TextOutCR();
		}
		else {
			m_rcTextMargins.left += m_TabSize;
			FillTo(m_rcTextMargins.left, ' ');
		}
	}

	m_rcTextMargins.left = oldMargin;
	m_bPrintingSectionHeaders = false;
}

void CEditOutputDev::SetCursorColumn(int column)
{
	HWND hWnd;
	DWORD start, end, combined;

	if (m_CurrentX < column) {
		FillTo(column, ' ');
	}
	else if (m_CurrentX > column) {
		// This is DESTRUCTIVE!!!
		// We need to delete the leading spaces here, since just moving
		// the margin doesn't really move the cursor in an edit window
		hWnd = GetDlgItem(m_hOutputDlg, IDC_SCREEN_EDIT);
		combined = SendMessage(hWnd, EM_GETSEL, NULL, NULL);
		ASSERT(combined != -1);
		start = LOWORD(combined);
		end = HIWORD(combined);
		//Edit_SetSel(hWnd, end - m_CurrentX + column, -1);
		SendMessage(hWnd, EM_SETSEL, end - m_CurrentX + column, -1);
		//Edit_ReplaceSel(hWnd, _T(""));
		SendMessage(hWnd, EM_REPLACESEL, FALSE, (LPARAM)_T(""));
		m_CurrentX = column;
	}
}

BOOL CALLBACK CEditOutputDev::ScreenDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	TString temp;
	temp.Format(_T("%d"), hwndDlg);
	VALUE vpDev = m_vHwndToDev[temp];
	if (vpDev.Type() != stInteger) {
		return FALSE;	// Could occur on WM_DESTROY when app is being killed
	}
	CEditOutputDev* pDev = (CEditOutputDev*)vpDev.Integer();

	switch (uMsg) {
		case WM_SIZE:
			MoveWindow(GetDlgItem(pDev->m_hOutputDlg, IDC_SCREEN_EDIT), 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
			break;
		case WM_CLOSE:
			if (::IsWindowVisible(pDev->m_hOutputDlg)) {
				::ShowWindow(pDev->m_hOutputDlg, SW_HIDE);	// Hide it, don't destroy it
				pDev->m_pInterp->DecOpenWindows();
			}
			return FALSE;
		case WM_COMMAND:
			if (HIWORD(wParam) == EN_ERRSPACE) {
				MessageBox(NULL, _T("Out of space in text edit"), _T("Error"), MB_OK);
				return FALSE;
			}
		default:
			return FALSE;
	}
	return TRUE;
}

void CEditOutputDev::PrintTable(const TTable& table)
{
	int* colWidths = new int[table.m_Cols];
	memset(colWidths, 0, sizeof(int) * table.m_Cols);
	DetermineColWidths(table, colWidths);

	TString str;
	for (int row = 0; row < table.m_Rows; ++row) {
		if (table.m_bEmptyRow[row] == false) {
			for (int col = 0; col < table.m_Cols; ++col) {
				if (table.m_bEmptyCol[col] == false) {
					// For left alignment,  use "%-s*s" as the format specifier
					// For right alignment, use "%*s" as the format specifier
					str.Format(_T("%-*s") , colWidths[col],
						(LPCTSTR)table.GetCell(row, col).contents);
					TextOut((LPCTSTR)str);
				}
			}
			TextOutCR();
		}
	}

	delete[] colWidths;
}

void CEditOutputDev::DetermineColWidths(const TTable& table, int colWidths[])
{
	// The array colWidths must be zero'd before calling this function
	int Length;
	for (int row = 0; row < table.m_Rows; ++row) {
		if (table.m_bEmptyRow[row] == false) {
			for (int col = 0; col < table.m_Cols; ++col) {
				if (table.m_bEmptyCol[col] == false) {
					Length = table.GetCell(row, col).contents.GetLength() + table.m_Pad;
					if (colWidths[col] < Length) {
						colWidths[col] = Length;
					}
				}
				else {
					colWidths[col] = 0;
				}
			}
		}
	}
}

//===========================================================================

#if !defined(DIALECT_LITE)

CMetaOutputDev::CMetaOutputDev(CInterpreter* pInterp)
{
	m_MetaViewer = new CMetaViewer(pInterp);

	bPrintingDoc = false;
	bPrintingPage = false;
	m_Meta = new CMeta(TString(_T("Dialect Meta Output")));

	m_hDC = CreateDC(_T("DISPLAY"), NULL, NULL, NULL);
	m_hFont = NULL;
	m_ptPixPerInch.x = GetDeviceCaps(m_hDC, LOGPIXELSX);
	m_ptPixPerInch.y = GetDeviceCaps(m_hDC, LOGPIXELSY);
	m_ptGutter.x = 5;
	m_ptGutter.y = 0;
	m_ptPageSize.x = (long)(8.5f * m_ptPixPerInch.x);
	m_ptPageSize.y = 11 * m_ptPixPerInch.y;
	m_TabSize = m_ptPixPerInch.x >> 2;	// Quarter inch per tab
	m_FillToSpot = 32;	// Used in SectionItem();
}

CMetaOutputDev::~CMetaOutputDev()
{
	EndDocument();

	if (m_hFont != NULL) {
		DeleteObject(m_hFont);				// Discard old font
	}
	DeleteDC(m_hDC);

	delete m_Meta;
	delete m_MetaViewer;
}

// Set/Get in units of inches...
float CMetaOutputDev::GetX(){return (float)m_CurrentX / (float)m_ptPixPerInch.x;}
float CMetaOutputDev::GetY(){return (float)m_CurrentY / (float)m_ptPixPerInch.y;}
void CMetaOutputDev::SetX(float x){m_CurrentX = (int)(x * m_ptPixPerInch.x);}
void CMetaOutputDev::SetY(float y){m_CurrentY = (int)(y * m_ptPixPerInch.y);}
float CMetaOutputDev::TextWidth(TString text) {
	SIZE extent;
	GetTextExtentPoint32(m_hDC, (LPCTSTR)text, text.GetLength(), &extent);
	return (float)extent.cx / (float)m_ptPixPerInch.x;
}

void CMetaOutputDev::SetMargins(float left, float top, float right, float bottom)
{
	int leftOffset = m_rcTextMargins.left - m_rcMargins.left;	// Current "tabbing" level

	// Convert inches to pixel values respective to top and left
	m_rcMargins.left = (int)ceil(left * (float)m_ptPixPerInch.x) - m_ptGutter.x;
	m_rcMargins.top = (int)ceil(top * (float)m_ptPixPerInch.y) - m_ptGutter.y;
	m_rcMargins.right = m_ptPageSize.x - (int)ceil(right * (float)m_ptPixPerInch.x) - m_ptGutter.x;
	m_rcMargins.bottom = m_ptPageSize.y - (int)ceil(bottom * (float)m_ptPixPerInch.y) - m_ptGutter.y;

	if (!m_bPrintedHeaders) {
		// We haven't printed anything yet, so we can still move the "cursor"
		// to the top-left corner without fear of overwritting previous text.
		m_CurrentX = m_rcMargins.left;
		m_CurrentY = m_rcMargins.top;
	}

	m_rcTextMargins = m_rcMargins;
	m_rcTextMargins.left = m_rcMargins.left + leftOffset;
}

void CMetaOutputDev::AbortDocument()
{
	m_Meta->AbortDocument();
	bPrintingPage = false;
	bPrintingDoc = false;
	m_bPrintedHeaders = false;
	if (m_SectHeaderTop != -1) {
		m_SectHeaderTop = -1;
		//SyntaxError(_T("Missing a matching 'output endsection' in document"));
	}
}

void CMetaOutputDev::EndDocument()
{
	if (!m_bPrintedHeaders && bPrintingDoc) {
		// If we haven't printer headers yet, and document is done, then
		// abort so that we don't print a blank page.
		AbortDocument();
	}
	else {
		if (bPrintingPage) {
			//::EndPage(m_hPrinterDC);
		}
		if (bPrintingDoc) {
			//char closing[] = "\r\n</BODY>\r\n</HTML>\r\n";
			//m_OutputFile.Write((void*)closing, 20);
			//m_OutputFile.Close();
		}
		bPrintingPage = false;
		bPrintingDoc = false;
		m_bPrintedHeaders = false;
		if (m_SectHeaderTop != -1) {
			EndAllSections();
			//m_SectHeaderTop = -1;
			//SyntaxError(_T("Missing a matching 'output endsection' in document"));
		}

		if (m_Meta->m_CurrentDoc >= 0) {
			m_MetaViewer->DisplayMeta(m_Meta);
		}
	}
}

void CMetaOutputDev::StartDocument(LPCTSTR docName)
{
	EndDocument();		// Be sure to end last doc first
	m_DocName = docName;

	m_MetaDoc = m_Meta->StartDocument(TString(docName));

	m_PageNumber = 0;	// The following NewPage() will increment this to 1
	ClearHeaders();
	SetFont(_T("Arial"), 8, FN_NORMAL);
	SetMargins(0.1f, 0, 2.0f, 1.9f);
	//SetFont("Courier New", 9, FN_NORMAL);

	bPrintingDoc = true;
	m_bPrintedHeaders = false;
	NewPage();
}

void CMetaOutputDev::ShowWindow()
{
	m_MetaViewer->ShowWindow(SW_SHOW);
}

void CMetaOutputDev::NewPage()
{
	if (bPrintingPage) {
		//::EndPage(m_hPrinterDC);
	}

	m_MetaPage = m_MetaDoc->NewPage(8.5f, 11.0f, TString(_T("Arial")), 8, FN_NORMAL, m_LineHeight);

	//m_bPrintedHeaders = false;
	bPrintingPage = true;
	m_PageNumber++;
	m_CurrentX = m_rcTextMargins.left;
	m_CurrentY = m_rcTextMargins.top;
}

void CMetaOutputDev::FillTo(int column, TCHAR fillChar)
{
	ASSERT(fillChar >= ' ');
	if (fillChar < ' ') return;

	// Column is in 10ths of an inch from paper edge (not margins)
	int rightX = m_rcMargins.left + column / 10 * m_ptPixPerInch.x;
	if (rightX < m_CurrentX) return;	// Nothing to do

	// Figure out how big the fillChar is...
	SIZE extent;
	GetTextExtentPoint32(m_hDC, &fillChar, 1, &extent);

	// Calc number of fillchars needed and create an array of them
	int NumChars = (rightX - m_CurrentX) / extent.cx;
	TCHAR *fillArray = new TCHAR[NumChars + 1];
	for (int idx = 0; idx < NumChars; ++idx) {
		fillArray[idx] = fillChar;
	}
	fillArray[NumChars] = 0;	// NULL terminate string

	// Force fill chars to line up on right side, not left
	m_CurrentX += (rightX - m_CurrentX) % extent.cx;

	_TextOut(fillArray);	// Use _TextOut since we don't have carraige returns
	delete[] fillArray;
}

void CMetaOutputDev::TextOut(LPCTSTR text)
{
	int length = _tcslen(text);
	TCHAR* buf = new TCHAR[length + 1];
	TCHAR* bufPtr = buf;
	for (int loop = 0; loop < length; ++loop) {
		if ((BYTE)((*bufPtr = text[loop])) >= ' ') ++bufPtr;
		else {
			// Assuming this is a '\n', '\r', or '\r\n'
			*bufPtr = 0;
			_TextOut(buf);
			TextOutCR();
			if (m_bDoingSItem) {
				FillTo(m_FillToSpot, ' ');
				_TextOut(_T(" "));
			}
			bufPtr = buf;
			if ((text[loop + 1] < ' ') && (text[loop] != text[loop + 1])) ++loop;
		}
	}
	*bufPtr = 0;
	_TextOut(buf);
	delete[] buf;
}

void CMetaOutputDev::_TextOut(LPCTSTR text)
{
	TCHAR buf[256];

	ASSERT(text != NULL);

	if (*text == 0) return;		// Empty string, nothing to print

	if (!m_bPrintedHeaders) {
		// Print page headers at the start of every page
		m_bPrintedHeaders = true;
		PrintPageHeaders();
	}

	if (!m_bPrintingHeadersNow) {
		if (m_SectHeaderTop >= 0) {
			if ((!m_bPrintingSectionHeaders) && (!m_SectionHeaders[m_SectHeaderTop].bPrinted)) {
				// PrintSectionHeaders() calls this TextOut() so we
				// had better kill the ensuing endless loop right now...
				PrintSectionHeaders();
			}
		}
	}

	SIZE extent;
	GetTextExtentPoint32(m_hDC, text, _tcslen(text), &extent);

	if (m_rcTextMargins.right - m_CurrentX >= extent.cx) {
		//TEXTOUT(m_hPrinterDC, m_CurrentX, m_CurrentY, text, strlen(text));
		m_MetaPage->NewTextEvent(m_CurrentX, m_CurrentY, TString(text), m_FontStyle);
		m_CurrentX += extent.cx;
	}
	else {
		int idx;
		wordBuffer = text;
		LPCTSTR s = NextWord(idx);
		while (idx > -1) {	// NextWord sets idx to -1 when nothing left
			GetTextExtentPoint32(m_hDC, s, idx, &extent);
			/*if (m_rcTextMargins.right - m_rcTextMargins.left < extent.cx) {
				// It's not going to fit after a CR anyway, so just print it...
			}
			else*/ if (m_rcTextMargins.right - m_CurrentX < extent.cx) {
				TextOutCR();
				// We need to "tab" to the wrap indent level here
				if (m_bDoingSItem) {
					FillTo(m_FillToSpot, ' ');
					_TextOut(_T(" "));	// One space to help align
				}
				//else {
					//SetColumn(m_WrapIndent);
				//	FillTo(m_WrapIndent, ' ');
				//}
				// After a carraige return, don't print preceeding spaces?
				while ((idx > 0) && (*s == ' ')) {
					s++;
					idx--;
				}
			}

			if (idx > -1) {
				if (idx > 0) {
					//TEXTOUT(m_hPrinterDC, m_CurrentX, m_CurrentY, s, idx);
					for(int qwerty = 0; qwerty < idx; ++qwerty) {
						if (qwerty >= 255) {
							InternalError(_T("Buffer too small in Meta text out"));
							break;
						}
						buf[qwerty] = s[qwerty];
					}
					buf[idx] = 0;
					m_MetaPage->NewTextEvent(m_CurrentX, m_CurrentY, TString(buf), m_FontStyle);
					m_CurrentX += extent.cx;
				}
				s = NextWord(idx);
			}
		}
	}
}

void CMetaOutputDev::TextOutTab()
{
	m_CurrentX += m_TabSize - (m_CurrentX - m_rcTextMargins.left) % m_TabSize;
	if (m_CurrentX > m_rcTextMargins.right) {
		m_CurrentX = m_rcTextMargins.right;
	}
}

void CMetaOutputDev::TextOutCR(int lineHeight)
{
	if (lineHeight < 0) lineHeight = m_LineHeight;

	if (m_CurrentY + lineHeight > m_rcTextMargins.bottom) {
		NewPage();
	}
	else {
		m_CurrentX = m_rcTextMargins.left;
		m_CurrentY += lineHeight;
	}
}

int CMetaOutputDev::LinesLeft()
{
	return (m_rcTextMargins.bottom - m_CurrentY) / m_LineHeight;
}

void CMetaOutputDev::SetFont(TString fontName, int fontSize, U32 fontStyle)
{
	LOGFONT fn;
#ifdef __WINCE__
	fn.lfHeight = -fontSize * GetDeviceCaps(m_hDC, LOGPIXELSY) / 72;
#else
	fn.lfHeight = -MulDiv(fontSize, GetDeviceCaps(m_hDC, LOGPIXELSY), 72);
#endif
	fn.lfWidth = 0;
	fn.lfEscapement = 0;
	fn.lfOrientation = 0;
	fn.lfWeight = fontStyle & FN_BOLD ? FW_BOLD : FW_NORMAL;
	fn.lfItalic = fontStyle & FN_ITALIC ? TRUE : FALSE;
	fn.lfUnderline = fontStyle & FN_UNDERLINE ? TRUE : FALSE;
	fn.lfStrikeOut = FALSE;
	fn.lfCharSet = DEFAULT_CHARSET;
	fn.lfOutPrecision = OUT_DEFAULT_PRECIS;
	fn.lfClipPrecision = CLIP_DEFAULT_PRECIS;
#ifdef __WINCE__
	fn.lfQuality = 0;
#else
	fn.lfQuality = PROOF_QUALITY;
#endif
	fn.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
	_tcscpy(fn.lfFaceName, (LPCTSTR)fontName);


	HFONT newFont = CreateFontIndirect(&fn);

	if (newFont == NULL) {
		MessageBox(NULL, _T("Failed to create font."), _T("Warning"), MB_OK);
	}
	else {
		m_FontName = fontName;
		m_FontSize = fontSize;
		m_FontStyle = fontStyle;
		if (!SelectObject(m_hDC, newFont)) {	// Select in new font
			OutputDebugString(_T("** Failed to select font into device\n"));
		}
		if (m_hFont != NULL) {
			DeleteObject(m_hFont);				// Discard old font
		}
		m_hFont = newFont;						// Save the pointer for later

		GetTextMetrics(m_hDC, &m_TextMetrics);
		m_LineHeight = m_TextMetrics.tmHeight + 2 * m_TextMetrics.tmExternalLeading;
	}
}

void CMetaOutputDev::PrintTable(const TTable& table)
{
	// First, let's make sure we really need to print this table
	bool bPrintingOK = false;
	int firstCol = table.m_bRowHeaders ? 1 : 0;
	for (int loop = firstCol; loop < table.m_Cols; ++loop) {
		if (table.m_bEmptyCol[loop] == false) {
			bPrintingOK = true;
			break;
		}
	}
	if (!bPrintingOK) return;

	int* colWidths = new int[table.m_Cols];
	int* rowHeights = new int[table.m_Rows];
	memset(colWidths, 0, sizeof(int) * table.m_Cols);
	memset(rowHeights, 0, sizeof(int) * table.m_Rows);
	DetermineColWidths(table, colWidths, rowHeights);

	int TableHeight = 0;
	for (loop = 0; loop < table.m_Rows; ++loop) {
		TableHeight += rowHeights[loop];
	}

	// Make sure we are all the way to the left margin
	if (m_CurrentX != m_rcTextMargins.left) TextOutCR();

	if (m_CurrentY + TableHeight > m_rcTextMargins.bottom) {
		// If the table won't fit completely on a page, start a new page
		NewPage();
	}

	// We don't want headers to print during a tables call to TextOut()
	// becuase we will have messed with the margins
	if (!m_bPrintedHeaders) PrintPageHeaders();
	if (!m_bPrintingHeadersNow) {
		if (m_SectHeaderTop >= 0) {
			if ((!m_bPrintingSectionHeaders) && (!m_SectionHeaders[m_SectHeaderTop].bPrinted)) {
				// PrintSectionHeaders() calls this TextOut() so we
				// had better kill the ensuing endless loop right now...
				PrintSectionHeaders();
			}
		}
	}

	TString str;
	for (int row = 0; row < table.m_Rows; ++row) {
		if (table.m_bEmptyRow[row] == false) {
			for (int col = 0; col < table.m_Cols; ++col) {
				if (table.m_bEmptyCol[col] == false) {
					int oldIndent = m_WrapIndent;
					m_WrapIndent = 0;
					RECT oldMargins = m_rcTextMargins;

					int startX = m_CurrentX;
					int startY = m_CurrentY;

					m_rcTextMargins.top = m_CurrentY;
					m_rcTextMargins.bottom = oldMargins.bottom;
					m_rcTextMargins.left = m_CurrentX;
					m_rcTextMargins.right = m_CurrentX + colWidths[col];

					TextOut((LPCTSTR)table.GetCell(row, col).contents);

					m_CurrentX = m_rcTextMargins.right + (m_ptPixPerInch.x >> 2);	// Quarter inch pad
					m_CurrentY = startY;

					m_rcTextMargins = oldMargins;
					m_WrapIndent = oldIndent;
				}
			}
			TextOutCR(rowHeights[row]);
		}
	}

	delete[] colWidths;
	delete[] rowHeights;
}

void CMetaOutputDev::DetermineColWidths(const TTable& table, int colWidths[], int rowHeights[])
{
	// The array colWidths must be zero'd before calling this function
	int Length;
	for (int row = 0; row < table.m_Rows; ++row) {
		if (table.m_bEmptyRow[row] == false) {
			for (int col = 0; col < table.m_Cols; ++col) {
				if (table.m_bEmptyCol[col] == false) {
					SIZE extent;
					GetTextExtentPoint32(m_hDC,
						(LPCTSTR)table.GetCell(row, col).contents,
						table.GetCell(row, col).contents.GetLength(),
						&extent);
					Length = extent.cx;
					if (colWidths[col] < Length) {
						colWidths[col] = Length;
					}
				}
				else {
					colWidths[col] = 0;
				}
			}
		}
	}

	DetermineRowHeights(table, colWidths, rowHeights);
}

void CMetaOutputDev::DetermineRowHeights(const TTable& table, int colWidths[], int rowHeights[])
{
	int totalWidth = 0, lastTotalWidth = 100000;
	float maxColWidth = 2.0f;	// Start with 2 inches

	// A hack job, try 'loop' number of times to reduce length
	for (int loop = 0; loop < 1000; ++loop) {
		totalWidth = 0;
		for (int col = 0; col < table.m_Cols; ++col) {
			if (col == table.m_Cols - 1)
				totalWidth += colWidths[col];	// Don't pad last cell
			else
				totalWidth += colWidths[col] + (m_ptPixPerInch.x >> 2);// Quarter inch pad
		}

		if (totalWidth == lastTotalWidth) {
			// Reduction isn't happening
			maxColWidth -= 0.2f;
		}
		lastTotalWidth = totalWidth;

		if (totalWidth > m_rcTextMargins.right - m_rcTextMargins.left) {
			// We're going to have to wrap at least one column
			for (int col = 0; col < table.m_Cols; ++col) {
				if (colWidths[col] > (int)(maxColWidth * m_ptPixPerInch.x)) {
					// For each column greater than maxColWidth inches, shorten 1/8 inch
					colWidths[col] -= (m_ptPixPerInch.x >> 2);
				}
			}
		}
		else break;
	}

	if (totalWidth > m_rcTextMargins.right - m_rcTextMargins.left) {
		InternalError(_T("Table too wide for page"));
	}

	int Height;
	for (int row = 0; row < table.m_Rows; ++row) {
		rowHeights[row] = 0;
		if (table.m_bEmptyRow[row] == false) {
			for (int col = 0; col < table.m_Cols; ++col) {
				if (table.m_bEmptyCol[col] == false) {
					/*SIZE extent;
					GetTextExtentPoint32(m_hDC,
						(LPCTSTR)table.GetCell(row, col).contents,
						table.GetCell(row, col).contents.GetLength(),
						&extent);*/
					// Number of text lines * line height
					if (colWidths[col] != 0) {
						/*if (extent.cx % colWidths[col] == 0) {
							Height = (extent.cx / colWidths[col]) * m_LineHeight;
						}
						else {
							Height = ((extent.cx / colWidths[col]) + 1) * m_LineHeight;
						}*/
						Height = HowManyLines((LPCTSTR)table.GetCell(row, col).contents, colWidths[col]) * m_LineHeight;
						if (rowHeights[row] < Height) {
							rowHeights[row] = Height;
						}
					}
				}
			}
		}
		else {
			rowHeights[row] = 0;
		}
	}
}

int CMetaOutputDev::HowManyLines(LPCTSTR text, int width)
{
	// Use for table row height calculations only!!!!!!!!
	if ((text == NULL) || (*text == '\0')) return 0;

	int lineCount = 1, currentX = 0;

	int length = _tcslen(text);
	TCHAR* buf = new TCHAR[length + 1];
	TCHAR* bufPtr = buf;
	for (int loop = 0; loop < length; ++loop) {
		if ((*bufPtr = text[loop]) >= ' ') ++bufPtr;
		else {
			// Assuming this is a '\n', '\r', or '\r\n'
			*bufPtr = 0;
			_HowManyLinesTextOut(buf, width, currentX, lineCount);
			// Carriage return
			++lineCount;
			currentX = 0;

			bufPtr = buf;
			if ((text[loop + 1] < ' ') && (text[loop] != text[loop + 1])) ++loop;
		}
	}
	*bufPtr = 0;
	if (*text != '\0') _HowManyLinesTextOut(buf, width, currentX, lineCount);

	delete[] buf;
	return lineCount;
}

void CMetaOutputDev::_HowManyLinesTextOut(LPCTSTR text, int width, int& currentX, int& lineCount)
{
	ASSERT(text != NULL);

	if (*text == 0) return;		// Empty string, nothing to print

	int textLength = _tcslen(text);
	SIZE extent;
	GetTextExtentPoint32(m_hDC, text, textLength, &extent);

	if (width - currentX >= extent.cx) {
		currentX += extent.cx;
	}
	else {
		int idx;
		wordBuffer = text;
		LPCTSTR s = NextWord(idx);
		while (idx > -1) {	// NextWord sets idx to -1 when nothing left
			GetTextExtentPoint32(m_hDC, s, idx, &extent);
			if (width - currentX < extent.cx) {
				// Carriage return
				++lineCount;
				currentX = 0;
				// After a carraige return, don't print preceeding spaces?
				while ((idx > 0) && (*s == ' ')) {
					s++;
					idx--;
				}
			}

			if (idx > -1) {
				if (idx > 0) {
					currentX += extent.cx;
				}
				s = NextWord(idx);
			}
		}
	}
}

//======================================================

VALUE CMetaViewer::m_vHwndToDev = AllocFrame(31);

CMetaViewer::CMetaViewer(CInterpreter* pInterp)
{
	m_pInterp = pInterp;
	m_ScrollHeight = 0;
	m_MetaWindow = NULL;
	m_TextWindow = NULL;
	m_DocComboBox = NULL;
	m_CloseButton = NULL;

	RegisterMetaWindow(NULL);	// Register the WNDCLASS

	// Do the majority of LOGFONT here to save time in OnPaint()
//#ifdef __WINCE__
//	m_fn.lfHeight = -fontSize * GetDeviceCaps(hDC, LOGPIXELSY) / 72;
//#else
//	m_fn.lfHeight = -MulDiv(fontSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
//#endif
	m_fn.lfWidth = 0;
	m_fn.lfEscapement = 0;
	m_fn.lfOrientation = 0;
	m_fn.lfWeight = FW_NORMAL;
	m_fn.lfItalic = FALSE;
	m_fn.lfUnderline = FALSE;
	m_fn.lfStrikeOut = FALSE;
	m_fn.lfCharSet = DEFAULT_CHARSET;
	m_fn.lfOutPrecision = OUT_DEFAULT_PRECIS;
	m_fn.lfClipPrecision = CLIP_DEFAULT_PRECIS;
#ifdef __WINCE__
	m_fn.lfQuality = 0;
#else
	m_fn.lfQuality = PROOF_QUALITY;
#endif
	m_fn.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
	_tcscpy(m_fn.lfFaceName, _T("Arial"));
}

CMetaViewer::~CMetaViewer()
{
	// Don't delete m_Meta since it belongs to the CMetaDev which created it
	if (IsWindow(m_MetaWindow)) {
		if (IsWindowVisible(m_MetaWindow)) {
			m_pInterp->DecOpenWindows();
		}
		DestroyWindow(m_MetaWindow);

		// Remove window handles from lookup table
		if (m_vHwndToDev.Frame() != NULL) {
			TString temp;
			temp.Format(_T("%d"), m_MetaWindow);
			m_vHwndToDev.Frame()->Remove(temp);
			temp.Format(_T("%d"), m_DocComboBox);
			m_vHwndToDev.Frame()->Remove(temp);
			temp.Format(_T("%d"), m_TextWindow);
			m_vHwndToDev.Frame()->Remove(temp);
		#ifdef __WINCE__
			temp.Format(_T("%d"), m_CloseButton);
			m_vHwndToDev.Frame()->Remove(temp);
		#endif
		}
	}
}

bool CMetaViewer::IsVisible()
{
	return ((m_MetaWindow != NULL) && (IsWindowVisible(m_MetaWindow)));
}

void CMetaViewer::ShowWindow(int show)
{
	if (IsWindow(m_MetaWindow) && !IsWindowVisible(m_MetaWindow)) {
		::ShowWindow(m_MetaWindow, show);
		m_pInterp->IncOpenWindows();
	}
}

void CMetaViewer::SetTitle(LPCTSTR title)
{
	SetWindowText(m_MetaWindow, title);
}

void CMetaViewer::DisplayMeta(CMeta* meta)
{
	m_Meta = meta;

	if (m_MetaWindow == NULL) {
		if (CreateMetaWindow(NULL, SW_SHOW) != NULL) {
			m_pInterp->IncOpenWindows();
		}
	}
	else {
		if (!IsWindow(m_TextWindow)) return;
		else if (!IsWindowVisible(m_MetaWindow)) {
			::ShowWindow(m_MetaWindow, SW_SHOW);
			m_pInterp->IncOpenWindows();
		}
	}

	// Fill combo box with list of scripts outputs
	CB_ResetContent(m_DocComboBox);
	for (int doc = 0; doc < MAX_META_DOCS; ++doc) {
		if (m_Meta->m_MetaDocs[doc] == NULL) break;
		CB_AddString(m_DocComboBox, m_Meta->m_MetaDocs[doc]->m_DocName);
	}

	// Set combo box to first script and adjust scroll bar size
	CB_SetCurSel(m_DocComboBox, 0);
	m_ScrollHeight = 0;
	doc = CB_GetCurSel(m_DocComboBox);
	if ((doc != CB_ERR) && (m_Meta->m_MetaDocs[doc] != NULL)) {
		for (int page = 0; page < MAX_META_PAGES; ++page) {
			if (m_Meta->m_MetaDocs[doc]->m_MetaPages[page] == NULL) break;
			m_ScrollHeight += m_Meta->m_MetaDocs[doc]->m_MetaPages[page]->dy();
			m_ScrollHeight += PAGE_BREAK_HEIGHT;
		}
	}

	RECT rc;
	GetWindowRect(m_TextWindow, &rc);

	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_ALL;
	si.nMin = 0;
	si.nMax = m_ScrollHeight;
	si.nPos = 0;
	si.nPage = rc.bottom - rc.top;
	SetScrollInfo(m_TextWindow, SB_VERT, &si, TRUE);
	InvalidateRect(m_TextWindow, NULL, TRUE);
}

BOOL CMetaViewer::RegisterMetaWindow(HINSTANCE hInstance) {
	WNDCLASS WndClass;

	// m_TextWindows class
	WndClass.style 			= 0;
	WndClass.lpfnWndProc  	= TextWndProc;
	WndClass.cbClsExtra    	= 0;
	WndClass.cbWndExtra    	= 0;
	WndClass.hInstance      = hInstance;
	WndClass.hIcon          = NULL;//LoadIcon(hInstance, MAKEINTRESOURCE(ICON_DESKLIST));
#ifdef __WINCE__
	WndClass.hCursor        = NULL;
#else
	WndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);
#endif
	WndClass.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
	WndClass.lpszMenuName   = NULL;
	WndClass.lpszClassName  = _T("MetaText");
	if (RegisterClass(&WndClass) == 0) return false;

	// The main m_MetaWindow class
	WndClass.lpfnWndProc  	= MetaWndProc;
	WndClass.lpszClassName  = _T("DialectMeta");

	return (RegisterClass(&WndClass) != 0);
}

HWND CMetaViewer::CreateMetaWindow(HINSTANCE hInstance, int nCmdShow) {
#ifdef __WINCE__
	m_MetaWindow = CreateWindow(_T("DialectMeta"), // registered class name
							_T("Dialect Output"),   			// title bar text
							WS_OVERLAPPED, // window style
							0, -25, 640, 240,
							//0, 0, 640, 215,
							NULL,        		// handle of parent window
							NULL,             // handle of menu or child window ID
							hInstance,        // handle of application instance
							NULL);            // address of window-creation data

	m_DocComboBox = CreateWindow(_T("COMBOBOX"), // registered class name
							_T("Dialect Output"),   			// title bar text
							WS_CHILD | CBS_DROPDOWNLIST | WS_VISIBLE | WS_VSCROLL, // window style
							0, 		// X
							0,		// Y
							300,    // Width
							100,    // Height
							m_MetaWindow,        		// handle of parent window
							(HMENU)3213,             // handle of menu or child window ID
							hInstance,        // handle of application instance
							NULL);            // address of window-creation data

	m_CloseButton = CreateWindow(_T("BUTTON"), // registered class name
							_T("Close"),   			// title bar text
							WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, // window style
							300, 		// X
							0,		// Y
							20,    // Width
							22,    // Height
							m_MetaWindow,        		// handle of parent window
							(HMENU)3263,             // handle of menu or child window ID
							hInstance,        // handle of application instance
							NULL);            // address of window-creation data

#else
	m_MetaWindow = CreateWindow(_T("DialectMeta"), // registered class name
							_T("Dialect Output"),   			// title bar text
							WS_OVERLAPPEDWINDOW, // window style
							0, 0, 640, 400,
							NULL,        		// handle of parent window
							NULL,             // handle of menu or child window ID
							hInstance,        // handle of application instance
							NULL);            // address of window-creation data

	m_DocComboBox = CreateWindow(_T("COMBOBOX"), // registered class name
							_T("Dialect Output"),   			// title bar text
							WS_CHILD | CBS_DROPDOWNLIST | WS_VISIBLE | WS_VSCROLL, // window style
							0, 		// X
							0,		// Y
							320,    // Width
							100,    // Height
							m_MetaWindow,        		// handle of parent window
							(HMENU)3213,             // handle of menu or child window ID
							hInstance,        // handle of application instance
							NULL);            // address of window-creation data

#endif

	m_TextWindow = CreateWindowEx(
							WS_EX_CLIENTEDGE,
							_T("MetaText"),				// registered class name
							NULL,   			// title bar text
							WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER, // window style
							0, 		// X
							30,		// Y
							320,    // Width
							180,    // Height
							m_MetaWindow,        		// handle of parent window
							(HMENU)3214,             // handle of menu or child window ID
							hInstance,        // handle of application instance
							NULL);            // address of window-creation data

	// Store the window handles so we can get back to this class
	TString temp;
	temp.Format(_T("%d"), m_MetaWindow);
	m_vHwndToDev.Set(temp, (int)this);
	temp.Format(_T("%d"), m_DocComboBox);
	m_vHwndToDev.Set(temp, (int)this);
	temp.Format(_T("%d"), m_TextWindow);
	m_vHwndToDev.Set(temp, (int)this);
#ifdef __WINCE__
	temp.Format(_T("%d"), m_CloseButton);
	m_vHwndToDev.Set(temp, (int)this);
#endif

	if (m_MetaWindow != NULL) ::ShowWindow(m_MetaWindow, nCmdShow);
	return m_MetaWindow;
}

LRESULT CALLBACK CMetaViewer::MetaWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	TString temp;
	temp.Format(_T("%d"), hWnd);
	VALUE vpDev = m_vHwndToDev[temp];
	if (vpDev.Type() != stInteger) {
		return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	CMetaViewer* pDev = (CMetaViewer*)vpDev.Integer();

	switch (msg) {
		case WM_KEYDOWN:
			if (wParam == VK_ESCAPE) {
				// Let's just hide it and let garbage collection do it's thing
				::ShowWindow(pDev->m_MetaWindow, SW_HIDE);
				pDev->m_pInterp->DecOpenWindows();
			}
			break;
		case WM_COMMAND:
			if ((HWND)lParam == pDev->m_CloseButton) {
				if (HIWORD(wParam) == BN_CLICKED) {
					// Let's just hide it and let garbage collection do it's thing
					::ShowWindow(pDev->m_MetaWindow, SW_HIDE);
					pDev->m_pInterp->DecOpenWindows();
				}
			}
			else if ((HWND)lParam == pDev->m_DocComboBox) {
				if (HIWORD(wParam) == CBN_SELCHANGE) {
					pDev->m_ScrollHeight = 0;
					int doc = CB_GetCurSel(pDev->m_DocComboBox);
					if ((doc != CB_ERR) && (pDev->m_Meta->m_MetaDocs[doc] != NULL)) {
						for (int page = 0; page < MAX_META_PAGES; ++page) {
							if (pDev->m_Meta->m_MetaDocs[doc]->m_MetaPages[page] == NULL) break;
							pDev->m_ScrollHeight += pDev->m_Meta->m_MetaDocs[doc]->m_MetaPages[page]->dy();
							pDev->m_ScrollHeight += PAGE_BREAK_HEIGHT;
						}
					}
					RECT rc;
					GetWindowRect(pDev->m_TextWindow, &rc);
					SCROLLINFO si;
					si.cbSize = sizeof(SCROLLINFO);
					si.fMask = SIF_ALL;
					si.nMin = 0;
					si.nMax = pDev->m_ScrollHeight;
					si.nPos = 0;
					si.nPage = rc.bottom - rc.top;
					SetScrollInfo(pDev->m_TextWindow, SB_VERT, &si, TRUE);
					InvalidateRect(pDev->m_TextWindow, NULL, TRUE);
				}
			}
			return TRUE;
		case WM_SIZE:
			if (wParam != SIZE_MINIMIZED) {
#ifndef __WINCE__
				MoveWindow(pDev->m_DocComboBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
#else
				MoveWindow(pDev->m_DocComboBox, 0, 0, LOWORD(lParam) - 50, HIWORD(lParam), TRUE);
				MoveWindow(pDev->m_CloseButton, LOWORD(lParam) - 50, 0, 50, 25, TRUE);
#endif
				RECT rc;
				GetWindowRect(pDev->m_DocComboBox, &rc);
				MoveWindow(pDev->m_TextWindow, 0, rc.bottom - rc.top + 3,
					LOWORD(lParam), (int)(HIWORD(lParam)) - 25, TRUE);

				// Now adjust the proportional scroll bar to new window size
				SCROLLINFO si;
				si.cbSize = sizeof(SCROLLINFO);
				si.fMask = SIF_PAGE;
				si.nPage = (int)(HIWORD(lParam)) - 25;
				SetScrollInfo(pDev->m_TextWindow, SB_VERT, &si, TRUE);
			}
			return TRUE;
		case WM_CLOSE:
			if (::IsWindowVisible(pDev->m_MetaWindow)) {
				::ShowWindow(pDev->m_MetaWindow, SW_HIDE);	// Hide it, don't destroy it
				pDev->m_pInterp->DecOpenWindows();
			}
			return 0;
	}
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

LRESULT CALLBACK CMetaViewer::TextWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	TString temp;
	temp.Format(_T("%d"), hWnd);
	VALUE vpDev = m_vHwndToDev[temp];
	if (vpDev.Type() != stInteger) return DefWindowProc(hWnd, msg, wParam, lParam);
	CMetaViewer* pDev = (CMetaViewer*)vpDev.Integer();

	switch (msg) {
		case WM_PAINT:
			pDev->OnPaint(hWnd, (HDC)wParam);
			return TRUE;
		case WM_VSCROLL:
			{
				SCROLLINFO si;
				si.cbSize = sizeof(SCROLLINFO);
				si.fMask = SIF_ALL;
				GetScrollInfo(pDev->m_TextWindow, SB_VERT, &si);

				switch ((int)LOWORD(wParam)) {
					case SB_THUMBPOSITION:
						SetScrollPos(pDev->m_TextWindow, SB_VERT, (short int)HIWORD(wParam), TRUE);
						InvalidateRect(pDev->m_TextWindow, NULL, TRUE);
						return 0;
					case SB_PAGEDOWN:
						{
							int offset = min(100, si.nMax - (int)si.nPage - si.nPos);
							if (offset > 0) {
								SetScrollPos(pDev->m_TextWindow, SB_VERT, si.nPos + offset, TRUE);
								ScrollWindowEx(pDev->m_TextWindow, 0, -offset, NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
							}
						}
						return 0;
					case SB_PAGEUP:
						if (si.nPos >= 100) {
							SetScrollPos(pDev->m_TextWindow, SB_VERT, si.nPos - 100, TRUE);
							ScrollWindowEx(pDev->m_TextWindow, 0, 100, NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
						}
						else {
							SetScrollPos(pDev->m_TextWindow, SB_VERT, 0, TRUE);
							ScrollWindowEx(pDev->m_TextWindow, 0, si.nPos, NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
						}
						return 0;
					case SB_LINEDOWN:
						if (si.nPos + (int)si.nPage < si.nMax) {
							SetScrollPos(pDev->m_TextWindow, SB_VERT, si.nPos + 20, TRUE);
							ScrollWindowEx(pDev->m_TextWindow, 0, -20, NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
						}
						return 0;
					case SB_LINEUP:
						if (si.nPos >= 20) {
							SetScrollPos(pDev->m_TextWindow, SB_VERT, si.nPos - 20, TRUE);
							ScrollWindowEx(pDev->m_TextWindow, 0, 20, NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
						}
						else {
							SetScrollPos(pDev->m_TextWindow, SB_VERT, 0, TRUE);
							ScrollWindowEx(pDev->m_TextWindow, 0, si.nPos, NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
						}
						return 0;
				}
			}
			break;
	}
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

void CMetaViewer::OnPaint(HWND hWnd, HDC hDC)
{
	if (m_Meta == NULL) return;

	RECT rc;
	if (!GetUpdateRect(hWnd, &rc, TRUE)) return;

	PAINTSTRUCT ps;
	hDC = BeginPaint(hWnd, &ps);

	U32 fontStyle = FN_NORMAL;
	LPCTSTR fontName = _T("Arial");
	int fontSize = 8;

	// This here instead of constructor because we don't have hdc yet
	// though I suppose we could get the "DISPLAY" hdc and do calc there
#ifdef __WINCE__
	m_fn.lfHeight = -fontSize * GetDeviceCaps(hDC, LOGPIXELSY) / 72;
#else
	m_fn.lfHeight = -MulDiv(fontSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
#endif

	// We should also start caching the fonts rather than recreating each time
	m_fn.lfWeight = FW_NORMAL;
	HFONT fontNormal = CreateFontIndirect(&m_fn);
	if (!SelectObject(hDC, fontNormal)) {	// Select in new font
		OutputDebugString(_T("** Failed to select font into device\n"));
	}

	m_fn.lfWeight = FW_BOLD;
	HFONT fontBold = CreateFontIndirect(&m_fn);
	if (fontBold == NULL) {
		OutputDebugString(_T("** Failed to create font\n"));
	}

	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_POS;
	GetScrollInfo(m_TextWindow, SB_VERT, &si);

	int PageOffset = 0;
	CTextMetaEvent* me;
	GetClientRect(hWnd, &rc);
	int doc = CB_GetCurSel(m_DocComboBox);
	if ((doc != CB_ERR) && (m_Meta->m_MetaDocs[doc] != NULL)) {
		for (int page = 0; page < MAX_META_PAGES; ++page) {
			if (m_Meta->m_MetaDocs[doc]->m_MetaPages[page] == NULL) break;
			for (int event = 0; event < MAX_EVENT_PER_PAGE; ++event) {
				me = (CTextMetaEvent*)m_Meta->m_MetaDocs[doc]->m_MetaPages[page]->m_MetaEvents[event];
				if ((me == NULL) || (rc.top >= rc.bottom + si.nPos)) break;
				rc.left = me->m_X;
				rc.top = me->m_Y + PageOffset - si.nPos;
				if (me->m_FontStyle != fontStyle) {
					switch (me->m_FontStyle) {
						case FN_BOLD:
							fontStyle = FN_BOLD;
							if (!SelectObject(hDC, fontBold)) {
								OutputDebugString(_T("** Failed to select font into device\n"));
							}
							break;
						default:	// FN_NORMAL
							fontStyle = FN_NORMAL;
							if (!SelectObject(hDC, fontNormal)) {
								OutputDebugString(_T("** Failed to select font into device\n"));
							}
					}
				}
				if (rc.top >= -100) {
					// Only call DrawText if "below" the top of the clip region
					DrawText(hDC, (LPCTSTR)me->m_Text, me->m_Text.GetLength(), &rc, 0);
				}
			}
			PageOffset += m_Meta->m_MetaDocs[doc]->m_MetaPages[page]->dy() + PAGE_BREAK_HEIGHT;
		}
	}

	DeleteObject(fontNormal);
	DeleteObject(fontBold);
	EndPaint(hWnd, &ps);
}

#endif // #if !defined(DIALECT_LITE)

//====================================================================

CRTFOutputDev::CRTFOutputDev()
{
	m_IndentLevel = 0;
	m_bDoc = false;
	m_bContSection = false;
	m_bDoingSItem = false;
	m_bAtLeftMargin = true;
	m_TabSize = 360;	// In twips
}

CRTFOutputDev::~CRTFOutputDev()
{
	EndDocument();
}

void CRTFOutputDev::StartDocument(LPCTSTR docName)
{
	EndDocument();		// Be sure to end last doc first
	m_DocName = docName;
	m_PageNumber = 0;	// The following NewPage() will increment this to 1
	ClearHeaders();

	if (m_DocName.IsEmpty()) {
		SyntaxError(_T("No document specified for RTF output"));
	}

	TCHAR buf[256];
	wsprintf(buf, _T("%s.rtf"), (LPCTSTR)m_DocName);
	GuaranteeFilePath(buf);	// First make sure the direcory exists
	m_OutputFile.Open(buf, TFile::create | TFile::write);

	char header[] = "{\\rtf1\\ansi\\deff0\\deftab360{\\fonttbl{\\f0\\fswiss MS Sans Serif;}{\\f1\\froman\\fcharset2 Symbol;}{\\f2\\froman Times New Roman;}{\\f3\\froman Times New Roman;}}{\\colortbl\\red0\\green0\\blue0;}\\deflang1033\\pard\\plain\\f2\\fs20 ";
	m_OutputFile.Write((void*)header, sizeof(header) - 1);	// -1 == Do not send NULL term

	m_bDoc = true;
	m_bAtLeftMargin = true;
}

void CRTFOutputDev::EndDocument()
{
	// We need to abort doc if no text

	if (m_bDoc) {
		char closing = '}';
		m_OutputFile.Write((void*)&closing, 1);
		m_OutputFile.Close();
		m_bDoc = false;
	}
	m_bPrintedHeaders = false;
	m_bContSection = false;
	m_bDoingSItem = false;
	if (m_SectHeaderTop != -1) {
		EndAllSections();
		//m_SectHeaderTop = -1;
		//SyntaxError(_T("Missing a matching 'output endsection' in document"));
	}
}

void CRTFOutputDev::AbortDocument()
{
	m_OutputFile.Close();
	TFile::DeleteFile(m_DocName + _T(".rtf"));	// Now delete it since we aborted

	m_bDoc = false;
	m_bContSection = false;
	m_bDoingSItem = false;
	m_bPrintedHeaders = false;
	if (m_SectHeaderTop != -1) {
		m_SectHeaderTop = -1;
		//SyntaxError(_T("Missing a matching 'output endsection' in document"));
	}
}

void CRTFOutputDev::TextOut(LPCTSTR text)
{
	ASSERT(text != NULL);

	if (*text == 0) return;		// Empty string, nothing to print

	if (!m_bPrintedHeaders) {
		// Print page headers at the start of every page
		m_bPrintedHeaders = true;
		PrintPageHeaders();
	}

	if (!m_bPrintingHeadersNow) {
		if (m_SectHeaderTop >= 0) {
			if ((!m_bPrintingSectionHeaders) && (!m_SectionHeaders[m_SectHeaderTop].bPrinted)) {
				// PrintSectionHeaders() calls this TextOut() so we
				// had better kill the ensuing endless loop right now...
				PrintSectionHeaders();
			}
		}
	}

	if ((!m_bDoingSItem) && (m_bContSection)) {
		// Previous TextOut calls were for a section item (table)
		// but this TextOut isn't, so end the sitem table
		m_bContSection = false;	// Must start new table next time
	}


#ifdef UNICODE
	int length = _tcslen(text);
	char* buf = new char[length];
	for (int idx = 0; idx < length; ++idx) {
		buf[idx] = (char)(text[idx]);
	}
	m_OutputFile.Write((void*)buf, length);
#else
	m_OutputFile.Write((void*)text, strlen(text));
#endif
}

void CRTFOutputDev::TextOutTab()
{
	m_OutputFile.Write("\\tab ", 5);
}

void CRTFOutputDev::TextOutCR(int lineHeight)
{
	if (lineHeight < 0) lineHeight = 1;

	for (int idx = 0; idx < lineHeight - 1; ++idx) {
		m_OutputFile.Write("\\par", 4);
	}

	TString out;
	out.Format(_T("\\par\\li%d "), m_IndentLevel);

#ifdef UNICODE
	char buf[32];
	for (idx = 0; idx < out.GetLength(); ++idx) {
		buf[idx] = (char)(out[idx]);
	}
	m_OutputFile.Write(buf, out.GetLength());
#else
	m_OutputFile.Write((LPCTSTR)out, out.GetLength());
#endif

	m_bAtLeftMargin = true;
}

int CRTFOutputDev::LinesLeft()
{
	return 10000;
}

void CRTFOutputDev::SetFont(TString fontName, int fontSize, U32 fontStyle)
{

}

void CRTFOutputDev::PrintTable(const TTable& table)
{

}

void CRTFOutputDev::FillTo(int column, TCHAR fillChar)
{
	TextOutTab();
}

void CRTFOutputDev::SectionItem(TString& label, TString& data, TCHAR fillChar)
{
	if (!m_bPrintingHeadersNow) {
		if (m_SectHeaderTop >= 0) {
			if ((!m_bPrintingSectionHeaders) && (!m_SectionHeaders[m_SectHeaderTop].bPrinted)) {
				// PrintSectionHeaders() calls this TextOut() so we
				// had better kill the ensuing endless loop right now...
				PrintSectionHeaders();
			}
		}
	}

	m_bDoingSItem = true;
	if (!m_bContSection && !m_bAtLeftMargin) {
		// The last textout wasn't a section item
		m_bContSection = true;
		m_OutputFile.Write("\\par ", 5);
	}

	TString out;
	out.Format(_T("{\\trowd\\trql\\trgaph108\\trleft%d\\cellx3200\\cellx8748 "),
		m_IndentLevel - 108);
#ifdef UNICODE
	char buf[128];
	for (int idx = 0; idx < out.GetLength(); ++idx) {
		buf[idx] = (char)(out[idx]);
	}
	m_OutputFile.Write((void*)buf, out.GetLength());
#else
	m_OutputFile.Write((LPCTSTR)out, out.GetLength());
#endif

	char cellStart[] = "\\pard\\intbl ";
	m_OutputFile.Write((void*)cellStart, sizeof(cellStart) - 1);
	TextOut(label);
	m_OutputFile.Write((void*)"\\cell ", 6);

	m_OutputFile.Write((void*)cellStart, sizeof(cellStart) - 1);
	if (!data.IsEmpty()) TextOut(data);
	m_OutputFile.Write((void*)"\\cell\\row}", 10);

	m_bDoingSItem = false;
}

void CRTFOutputDev::SendLineIndent(int indent)
{
	TString out;
	out.Format(_T("\\li%d "), indent);
#ifdef UNICODE
	char buf[64];
	for (int idx = 0; idx < out.GetLength(); ++idx) {
		buf[idx] = (char)(out[idx]);
	}
	m_OutputFile.Write(buf, out.GetLength());
#else
	m_OutputFile.Write((LPCTSTR)out, out.GetLength());
#endif
}

void CRTFOutputDev::PrintSectionHeaders()
{
	// Start at header zero and print any headers not printed yet
	// The current creation and destruction of fonts is slow,
	// unless Windows is caching these things... fix this later...

	m_bPrintingSectionHeaders = true;

	// Don't forget about text margins, etc
	RECT oldMargins = m_rcTextMargins;
	m_rcTextMargins = m_rcMargins;
	U32 oldFontStyle = m_FontStyle;
	int oldIndentLevel = m_IndentLevel;
	m_IndentLevel = 0;

	if (m_bPrintedHeaders) {
		// Only do this if not at very top of page
		TextOutCR();
	}

	for (int idx = 0; idx <= m_SectHeaderTop; ++idx) {
		if (!m_SectionHeaders[idx].bPrinted) {
			m_SectionHeaders[idx].bPrinted = true;
			if (m_SectionHeaders[idx].style != m_FontStyle) {
				SetFont(m_FontName, m_FontSize, m_SectionHeaders[idx].style);
			}
			m_OutputFile.Write((void*)"\\b ", 3);
			SendLineIndent(m_IndentLevel);
			TextOut(m_SectionHeaders[idx].label);
			m_OutputFile.Write((void*)"\\b0 ", 4);
			if (m_FontStyle != oldFontStyle) {
				SetFont(m_FontName, m_FontSize, oldFontStyle);
			}
			if (!m_SectionHeaders[idx].data.IsEmpty()) {
				FillTo(m_FillToSpot, '.');
				TextOut(_T("  "));
				TextOut(m_SectionHeaders[idx].data);
			}
			m_IndentLevel += m_TabSize;
			TextOutCR();
			m_IndentLevel -= m_TabSize;
		}
		m_IndentLevel += m_TabSize;
	}

	m_rcTextMargins = oldMargins;
	m_CurrentX = oldMargins.left;
	//m_IndentLevel = oldIndentLevel;

	if (m_FontStyle != oldFontStyle) {
		m_FontStyle = oldFontStyle;
		SetFont(m_FontName, m_FontSize, m_FontStyle);
	}

	m_bPrintingSectionHeaders = false;
}

void CRTFOutputDev::StartSection(TString& label, TString& data, U32 style)
{
	if (m_SectHeaderTop >= MAX_SECTION_HEADERS - 1) {
		InternalError(_T("Can't support anymore nested sections in output device"));
	}
	else {
		++m_SectHeaderTop;
		m_IndentLevel += m_TabSize;
		m_SectionHeaders[m_SectHeaderTop].label = label;
		m_SectionHeaders[m_SectHeaderTop].data = data;
		m_SectionHeaders[m_SectHeaderTop].style = style;
		m_SectionHeaders[m_SectHeaderTop].bPrinted = false;
		if (!data.IsEmpty()) {
			// This item is always going to print, so do it now
			PrintSectionHeaders();
		}
	}
}

void CRTFOutputDev::EndSection()
{
	if (m_SectHeaderTop >= 0) {
		--m_SectHeaderTop;
		if (m_bContSection) {
			// Close out the sitem table
			//m_OutputFile.Write((void*)"</TABLE>\r\n", 10);
			m_bContSection = false;	// Must start new table next time
		}
		m_IndentLevel -= m_TabSize;
		//SendLineIndent(m_IndentLevel);
		if (m_SectionHeaders[m_SectHeaderTop+1].bPrinted == true) {
			// Only need the carraige return if something printed in the section
			TextOutCR();
		}
	}
	else {
		SyntaxError(_T("Too many EndSections called on output device"));
	}
}

//====================================================================

HWND CreateEditWndDlg(HINSTANCE hinst, HWND hwndOwner)
{
	// The first step is to allocate memory to define the dialog.  The information to be
	// stored in the allocated buffer is the following:
	//
	// 1.  DLGTEMPLATE structure
	// 2.    0x0000 (Word) indicating the dialog has no menu
	// 3.    0x0000 (Word) Let windows assign default class to the dialog
	// 4.    (Caption)  Null terminated unicode string
	// 5.	 0x000B  (size of the font to be used)
	// 6.    "Arial"  (name of the typeface to be used)
	// 7.  DLGITEMTEMPLATE structure for the button	 (HAS TO BE DWORD ALIGNED)
	// 8.	 0x0080  to indicate the control is a button
	// 9.    (Title). Unicode null terminated string with the caption
	// 10.    0x0000   0 extra bytes of data for this control
	// 11.  DLGITEMTEMPLATE structure for the Static Text  (HAS TO BE DWORD ALIGNED)
	// 12.    0x0081 to indicate the control is static text
	// 13.   (Title). Unicode null terminated string with the text
	// 14     0x0000.  0 extra bytes of data for this control
	// 15. DLGITEMTEMPLATE structure for the Edit Control (HAS TO BE DWORD ALIGNED)
	// 16.   0x0082 to indicate an Edit control
	// 17.   (Text) - Null terminated unicode string to appear in the edit control
	// 18.   0x0000. 0 extra bytes of data for this control

	WCHAR szBoxCaption[] = L"Dialect Output";
	WCHAR szFontName[] = L"Courier New";

	int nBufferSize = 1024;	// Just guess, better too large than too small

	HLOCAL hLocal = LocalAlloc(LHND, nBufferSize);
	if (hLocal == NULL)
		return NULL;

	BYTE*	pBuffer = (BYTE*)LocalLock(hLocal);
	if (pBuffer == NULL)
	{
		LocalFree(hLocal);
		return NULL;
	}

	DLGTEMPLATE dlgTempl;
	// now initialize the DLGTEMPLATE structure
#ifdef __WINCE__
#	ifdef _WIN32_WCE_PSPC
		dlgTempl.style = WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_SETFONT;
		dlgTempl.cx = 120;
		dlgTempl.cy = 130;
#	else
		dlgTempl.style = WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_SETFONT;
		dlgTempl.cx = 250;
		dlgTempl.cy = 110;
#	endif
#else
	dlgTempl.style = WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP |
		WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | DS_SETFONT;
	dlgTempl.cx = 250;
	dlgTempl.cy = 200;
#endif
	dlgTempl.dwExtendedStyle = 0;
	dlgTempl.x = 0;
	dlgTempl.y = 0;
	dlgTempl.cdit = 1;  // 1 dialog items in the dialog


	BYTE*	pdest = pBuffer;
	// transfer DLGTEMPLATE structure to the buffer
	memcpy(pdest, &dlgTempl, sizeof(DLGTEMPLATE));
	pdest += sizeof(DLGTEMPLATE);
	*(WORD*)pdest = 0; // no menu
	pdest += sizeof(WORD);
	*(WORD*)pdest = 0;  // use default window class
	pdest += sizeof(WORD);
	memcpy(pdest, szBoxCaption, sizeof(szBoxCaption));
	pdest += sizeof(szBoxCaption);
	*(WORD*)pdest = 8;  // font size
	pdest += sizeof(WORD);
	memcpy(pdest, szFontName, sizeof(szFontName));
	pdest += sizeof(szFontName);

	/////////////////////////////////////////////
	/////////////////////////////////////////////
	DLGITEMTEMPLATE dlgItemTemp;
	dlgItemTemp.x  = 0; dlgItemTemp.y  = 0;
	dlgItemTemp.cx = dlgTempl.cx; dlgItemTemp.cy = dlgTempl.cy;
	dlgItemTemp.id = IDC_SCREEN_EDIT;
	dlgItemTemp.style = WS_CHILD | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN |
		WS_VSCROLL | WS_HSCROLL | WS_VISIBLE | WS_BORDER;
	dlgItemTemp.dwExtendedStyle = WS_EX_CLIENTEDGE;


	pdest = (BYTE*)(((DWORD)pdest + 3) & ~3);  // make the pointer DWORD aligned
	memcpy(pdest, (void *)&dlgItemTemp, sizeof(DLGITEMTEMPLATE));

	pdest += sizeof(DLGITEMTEMPLATE);
	*(WORD*)pdest = 0xFFFF;  // indicating atom value
	pdest += sizeof(WORD);
	*(WORD*)pdest = 0x0081;	// atom value for the edit control
	pdest += sizeof(WORD);

	// transfer the caption even when it is an empty string
	WCHAR*	pchCaption;
	int		nChars, nActualChars;

	nChars = 3;
	pchCaption = new WCHAR[nChars];
	nActualChars = MultiByteToWideChar(CP_ACP, 0, "", -1, pchCaption, nChars);
	memcpy(pdest, pchCaption, nActualChars * sizeof(WCHAR));
	pdest += nActualChars * sizeof(WCHAR);
	delete pchCaption;

	*(WORD*)pdest = 0;  // How many bytes in data for control
	pdest += sizeof(WORD);
	/////////////////////////////////////////////
	/////////////////////////////////////////////

	HWND hWnd = CreateDialogIndirect(NULL, (DLGTEMPLATE*)pBuffer, hwndOwner,
		(DLGPROC)CEditOutputDev::ScreenDlgProc);

	LocalUnlock(hLocal);
	LocalFree(hLocal);

	return hWnd;
}
